/*
 * sftp.c: SFTP generic client code.
 * Changed from synchronous to asynchronous mode
*	Added multithreading support/safety
* Added using of SSH channels 
* Added sophisticated dir listing procedure
* Allowed subsystem to be interactive, and integrated into complex frameworks
* And using full potential of SFTP protocol
* main change: separating from global variables by implementing scp_tag structure
* - remote - name of remote file to process. it can be directory name. When null, SCP backend quits
* - local - name of local file to process.
* - chan - ID of a channel (currently, 1 or 2). Designated in frontend. 
* - sync - event to syncronise between threads. Frontend should call SetEvent when any data is received and processed
* - hwnd, sktree - always same for single SSH connection, for any channel. Handle to program window and socket tree
* - destfile - returned (from SFTP) file name during download. Should be accepted (set to local filename) or declined (set to NULL)
 */

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include <time.h>

#include <network.h>
#include "bufchain.h"
#include "tree234.h"
#include "sshlib.h"
#include "ssh.h"
#include "scp.h"

static const Backend *back = &ssh_backend;		/*		scp can't use any other backend   */

#define GET_32BIT_MSB_FIRST(cp) \
  (((unsigned long)(char)(cp)[0] << 24) | \
  ((unsigned long)(char)(cp)[1] << 16) | \
  ((unsigned long)(char)(cp)[2] << 8) | \
  ((unsigned long)(char)(cp)[3]))

#define GET_32BIT(cp) GET_32BIT_MSB_FIRST(cp)

#define PUT_32BIT_MSB_FIRST(cp, value) ( \
  (cp)[0] = (char)((value) >> 24), \
  (cp)[1] = (char)((value) >> 16), \
  (cp)[2] = (char)((value) >> 8), \
  (cp)[3] = (char)(value) )

#define PUT_32BIT(cp, value) PUT_32BIT_MSB_FIRST(cp, value)

#define TIME_POSIX_TO_WIN(t, ft) (*(LONGLONG*)&(ft) = \
	((LONGLONG) (t) + (LONGLONG) 11644473600) * (LONGLONG) 10000000)
#define TIME_WIN_TO_POSIX(ft, t) ((t) = (unsigned long) \
	((*(LONGLONG*)&(ft)) / (LONGLONG) 10000000 - (LONGLONG) 11644473600))


char *dupcat(const char *s1, ...);
wchar_t *dupscat(const wchar_t *s1, ...);
char *dupvprintf(char *fmt, va_list ap);
char *dupprintf(char *fmt, ...);
char *dupstr(char *s);
extern "C" BOOL register_callback(int flag, SSH_CALLBACK_PROC proc);
extern SSH_CALLBACK_PROC callback_proc[16];
scphandler_fn_t scp_packet_dispatch[256];
void scp_buffer_consume(scp_tag *scp);
void notify_threadqueue(Ssh ssh, const int mesg, int is_stderr, int channel, const char *buf, int len) ;

struct RFile {
    HANDLE h;
};



enum {
    FILE_TYPE_NONEXISTENT, FILE_TYPE_FILE, FILE_TYPE_DIRECTORY, FILE_TYPE_WEIRD
};
enum {
    WCTYPE_NONEXISTENT, WCTYPE_FILENAME, WCTYPE_WILDCARD
};

#define SCP_SINK_FILE   1
#define SCP_SINK_DIR    2
#define SCP_SINK_ENDDIR 3
#define SCP_SINK_RETRY  4	       /* not an action; just try again */

uint64 uint64_div10(uint64 x, int *remainder)
{
    uint64 y;
    int rem, r2;
    y.hi = x.hi / 10;
    y.lo = x.lo / 10;
    rem = x.lo % 10;
    /*
     * Now we have to add in the remainder left over from x.hi.
     */
    r2 = x.hi % 10;
    y.lo += r2 * 2 * (0x80000000 / 10);
    rem += r2 * 2 * (0x80000000 % 10);
    y.lo += rem / 10;
    rem %= 10;

    if (remainder)
	*remainder = rem;
    return y;
}

void uint64_decimal(uint64 x, char *buffer)
{
    char buf[20];
    int start = 20;
    int d;

    do {
	x = uint64_div10(x, &d);
	assert(start > 0);
	buf[--start] = d + '0';
    } while (x.hi || x.lo);

    memcpy(buffer, buf + start, sizeof(buf) - start);
    buffer[sizeof(buf) - start] = '\0';
}

uint64 uint64_make(unsigned long hi, unsigned long lo)
{
    uint64 y;
    y.hi = hi;
    y.lo = lo;
    return y;
}

uint64 uint64_add(uint64 x, uint64 y)
{
    x.lo += y.lo;
    x.hi += y.hi + (x.lo < y.lo ? 1 : 0);
    return x;
}

uint64 uint64_add32(uint64 x, unsigned long y)
{
    uint64 yy;
    yy.hi = 0;
    yy.lo = y;
    return uint64_add(x, yy);
}

int uint64_compare(uint64 x, uint64 y)
{
    if (x.hi != y.hi)
	return x.hi < y.hi ? -1 : +1;
    if (x.lo != y.lo)
	return x.lo < y.lo ? -1 : +1;
    return 0;
}

uint64 uint64_subtract(uint64 x, uint64 y)
{
    x.lo -= y.lo;
    x.hi -= y.hi + (x.lo > ~y.lo ? 1 : 0);
    return x;
}

double uint64_to_double(uint64 x)
{
    return (4294967296.0 * x.hi) + (double)x.lo;
}

uint64 uint64_shift_right(uint64 x, int shift)
{
    if (shift < 32) {
	x.lo >>= shift;
	x.lo |= (x.hi << (32-shift));
	x.hi >>= shift;
    } else {
	x.lo = x.hi >> (shift-32);
	x.hi = 0;
    }
    return x;
}

uint64 uint64_shift_left(uint64 x, int shift)
{
    if (shift < 32) {
	x.hi <<= shift;
	x.hi |= (x.lo >> (32-shift));
	x.lo <<= shift;
    } else {
	x.hi = x.lo << (shift-32);
	x.lo = 0;
    }
    return x;
}

uint64 uint64_from_decimal(char *str)
{
    uint64 ret;
    ret.hi = ret.lo = 0;
    while (*str >= '0' && *str <= '9') {
	ret = uint64_add(uint64_shift_left(ret, 3),
			 uint64_shift_left(ret, 1));
	ret = uint64_add32(ret, *str - '0');
	str++;
    }
    return ret;
}


struct sftp_packet {
    char *data;
    unsigned length, maxlen;
    unsigned savedpos;
    int type;
};


static void fxp_internal_error(char *msg, scp_tag *scp);

/* ----------------------------------------------------------------------
 * SFTP packet construction functions.
 */
static void sftp_pkt_ensure(struct sftp_packet *pkt, int length)
{
    if ((int)pkt->maxlen < length) {
	pkt->maxlen = length + 256;
	pkt->data = memrealloc(pkt->data, pkt->maxlen, char);
    }
}
static void sftp_pkt_adddata(struct sftp_packet *pkt, void *data, int len)
{
    pkt->length += len;
    sftp_pkt_ensure(pkt, pkt->length);
    memcpy(pkt->data + pkt->length - len, data, len);
}
static void sftp_pkt_addbyte(struct sftp_packet *pkt, char byte)
{
    sftp_pkt_adddata(pkt, &byte, 1);
}
static struct sftp_packet *sftp_pkt_init(int pkt_type)
{
    struct sftp_packet *pkt;
    pkt = memalloc(struct sftp_packet);
    pkt->data = NULL;
    pkt->savedpos = -1;
    pkt->length = 0;
    pkt->maxlen = 0;
    sftp_pkt_addbyte(pkt, (char) pkt_type);
    return pkt;
}
/*
static void sftp_pkt_addbool(struct sftp_packet *pkt, char value)
{
    sftp_pkt_adddata(pkt, &value, 1);
}
*/
static void sftp_pkt_adduint32(struct sftp_packet *pkt,
			       unsigned long value)
{
    char x[4];
    PUT_32BIT(x, value);
    sftp_pkt_adddata(pkt, x, 4);
}
static void sftp_pkt_adduint64(struct sftp_packet *pkt, uint64 value)
{
    char x[8];
    PUT_32BIT(x, value.hi);
    PUT_32BIT(x + 4, value.lo);
    sftp_pkt_adddata(pkt, x, 8);
}
static void sftp_pkt_addstring_start(struct sftp_packet *pkt)
{
    sftp_pkt_adduint32(pkt, 0);
    pkt->savedpos = pkt->length;
}
static void sftp_pkt_addstring_str(struct sftp_packet *pkt, char *data)
{
    sftp_pkt_adddata(pkt, data, strlen(data));
    PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
}
static void sftp_pkt_addstringdata(struct sftp_packet *pkt,
				    char *data, int len)
{
    sftp_pkt_adddata(pkt, data, len);
    PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
}
static void sftp_pkt_addstring(struct sftp_packet *pkt, char *data)
{
    sftp_pkt_addstring_start(pkt);
    sftp_pkt_addstring_str(pkt, data);
}
static void sftp_pkt_addattrs(struct sftp_packet *pkt, struct fxp_attrs attrs)
{
    sftp_pkt_adduint32(pkt, attrs.flags);
    if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) {
	sftp_pkt_adduint32(pkt, attrs.size.hi);
	sftp_pkt_adduint32(pkt, attrs.size.lo);
    }
    if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) {
	sftp_pkt_adduint32(pkt, attrs.uid);
	sftp_pkt_adduint32(pkt, attrs.gid);
    }
    if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
	sftp_pkt_adduint32(pkt, attrs.permissions);
    }
    if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) {
	sftp_pkt_adduint32(pkt, attrs.atime);
	sftp_pkt_adduint32(pkt, attrs.mtime);
    }
    if (attrs.flags & SSH_FILEXFER_ATTR_EXTENDED) {
	/*
	 * We currently don't support sending any extended
	 * attributes.
	 */
    }
}

/* ----------------------------------------------------------------------
 * SFTP packet decode functions.
 */

static int sftp_pkt_getbyte(struct sftp_packet *pkt, char *ret)
{
    if (pkt->length - pkt->savedpos < 1)
	return 0;
    *ret = (char) pkt->data[pkt->savedpos];
    pkt->savedpos++;
    return 1;
}
static int sftp_pkt_getuint32(struct sftp_packet *pkt, unsigned long *ret)
{
    if (pkt->length - pkt->savedpos < 4)
	return 0;
    *ret = GET_32BIT(pkt->data + pkt->savedpos);
    pkt->savedpos += 4;
    return 1;
}
static int sftp_pkt_getstring(struct sftp_packet *pkt,
			      char **p, int *length)
{
    *p = NULL;
    if (pkt->length - pkt->savedpos < 4)
	return 0;
    *length = GET_32BIT(pkt->data + pkt->savedpos);
    pkt->savedpos += 4;
    if ((int)(pkt->length - pkt->savedpos) < *length || *length < 0) {
	*length = 0;
	return 0;
    }
    *p = pkt->data + pkt->savedpos;
    pkt->savedpos += *length;
    return 1;
}
static int sftp_pkt_getattrs(struct sftp_packet *pkt, struct fxp_attrs *ret)
{
    if (!sftp_pkt_getuint32(pkt, &ret->flags))
	return 0;
    if (ret->flags & SSH_FILEXFER_ATTR_SIZE) {
	unsigned long hi, lo;
	if (!sftp_pkt_getuint32(pkt, &hi) ||
	    !sftp_pkt_getuint32(pkt, &lo))
	    return 0;
	ret->size = uint64_make(hi, lo);
    }
    if (ret->flags & SSH_FILEXFER_ATTR_UIDGID) {
	if (!sftp_pkt_getuint32(pkt, &ret->uid) ||
	    !sftp_pkt_getuint32(pkt, &ret->gid))
	    return 0;
    }
    if (ret->flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
	if (!sftp_pkt_getuint32(pkt, &ret->permissions))
	    return 0;
    }
    if (ret->flags & SSH_FILEXFER_ATTR_ACMODTIME) {
	if (!sftp_pkt_getuint32(pkt, &ret->atime) ||
	    !sftp_pkt_getuint32(pkt, &ret->mtime))
	    return 0;
    }
    if (ret->flags & SSH_FILEXFER_ATTR_EXTENDED) {
	unsigned long count;
	if (!sftp_pkt_getuint32(pkt, &count))
	    return 0;
	while (count--) {
	    char *str;
	    int len;
	    /*
	     * We should try to analyse these, if we ever find one
	     * we recognise.
	     */
	    if (!sftp_pkt_getstring(pkt, &str, &len) ||
		!sftp_pkt_getstring(pkt, &str, &len))
		return 0;
	}
    }
    return 1;
}
static void sftp_pkt_free(struct sftp_packet *pkt)
{
    if (pkt->data)
	memfree(pkt->data);
    memfree(pkt);
}

/* ----------------------------------------------------------------------
 * Send and receive packet functions.
 */
int sftp_send(struct sftp_packet *pkt, scp_tag *scp)
{
    int ret;
    char x[4];
    PUT_32BIT(x, pkt->length);
    ret = (sftp_senddata(x, 4, scp) && sftp_senddata(pkt->data, pkt->length, scp));
    sftp_pkt_free(pkt);
//	if (want_reply) scp->crstate=line; else scp->crstate==0;
    return ret;
}

/* ----------------------------------------------------------------------
 * Request ID allocation and temporary dispatch routines.
 */

#define REQUEST_ID_OFFSET 256

struct sftp_request {
    unsigned id;
    int registered;
    void *userdata;
};

static int sftp_reqcmp(void *av, void *bv)
{
    struct sftp_request *a = (struct sftp_request *)av;
    struct sftp_request *b = (struct sftp_request *)bv;
    if (a->id < b->id)
	return -1;
    if (a->id > b->id)
	return +1;
    return 0;
}
static int sftp_reqfind(void *av, void *bv)
{
    unsigned *a = (unsigned *) av;
    struct sftp_request *b = (struct sftp_request *)bv;
    if (*a < b->id)
	return -1;
    if (*a > b->id)
	return +1;
    return 0;
}

static tree234 *sftp_requests;

static struct sftp_request *sftp_alloc_request(void)
{
    unsigned low, high, mid;
    int tsize;
    struct sftp_request *r;

    if (sftp_requests == NULL)
	sftp_requests = newtree234(sftp_reqcmp);

    /*
     * First-fit allocation of request IDs: always pick the lowest
     * unused one. To do this, binary-search using the counted
     * B-tree to find the largest ID which is in a contiguous
     * sequence from the beginning. (Precisely everything in that
     * sequence must have ID equal to its tree index plus
     * REQUEST_ID_OFFSET.)
     */
    tsize = count234(sftp_requests);

    low = -1;
    high = tsize;
    while (high - low > 1) {
	mid = (high + low) / 2;
	r = (sftp_request*)index234(sftp_requests, mid);
	if (r->id == mid + REQUEST_ID_OFFSET)
	    low = mid;		       /* this one is fine */
	else
	    high = mid;		       /* this one is past it */
    }
    /*
     * Now low points to either -1, or the tree index of the
     * largest ID in the initial sequence.
     */
    {
	unsigned i = low + 1 + REQUEST_ID_OFFSET;
	assert(NULL == find234(sftp_requests, &i, sftp_reqfind));
    }

    /*
     * So the request ID we need to create is
     * low + 1 + REQUEST_ID_OFFSET.
     */
    r = memalloc(struct sftp_request);
    r->id = low + 1 + REQUEST_ID_OFFSET;
    r->registered = 0;
    r->userdata = NULL;
    add234(sftp_requests, r);
    return r;
}

void sftp_cleanup_request(void)
{
    if (sftp_requests != NULL) {
	freetree234(sftp_requests);
	sftp_requests = NULL;
    }
}

void sftp_register(struct sftp_request *req)
{
    req->registered = 1;
}

struct sftp_request *sftp_find_request(struct sftp_packet *pktin, scp_tag *scp)
{
    unsigned long id;
    struct sftp_request *req;

    if (!pktin) {
	fxp_internal_error("did not receive a valid SFTP packet\n", scp);
	return NULL;
    }

    if (!sftp_pkt_getuint32(pktin, &id)) {
	fxp_internal_error("did not receive a valid SFTP packet\n", scp);
	return NULL;
    }
    req = (sftp_request*)find234(sftp_requests, &id, sftp_reqfind);

    if (!req || !req->registered) {
	fxp_internal_error("request ID mismatch\n", scp);
        sftp_pkt_free(pktin);
	return NULL;
    }

    del234(sftp_requests, req);

    return req;
}

/* ----------------------------------------------------------------------
 * String handling routines.
 */

static char *mkstr(char *s, int len)
{
    char *p = memallocn(len + 1, char);
    memcpy(p, s, len);
    p[len] = '\0';
    return p;
}

static wchar_t *mkwcs(char *s, int len)
{
    wchar_t *p = memallocn(len + 1, wchar_t);
    wsprintf(p, L"%S", s);
    p[len] = L'\0';
    return p;
}


/* ----------------------------------------------------------------------
 * SFTP primitives.
 */

/*
 * Deal with (and free) an FXP_STATUS packet. Return 1 if
 * SSH_FX_OK, 0 if SSH_FX_EOF, and -1 for anything else (error).
 * Also place the status into fxp_errtype.
 */
static int fxp_got_status(struct sftp_packet *pktin, scp_tag *scp)
{
    static const char *const messages[] = {
	/* SSH_FX_OK. The only time we will display a _message_ for this
	 * is if we were expecting something other than FXP_STATUS on
	 * success, so this is actually an error message! */
	"unexpected OK response",
	"end of file",
	"no such file or directory",
	"permission denied",
	"failure",
	"bad message",
	"no connection",
	"connection lost",
	"operation unsupported",
    };

    if (pktin->type != SSH_FXP_STATUS) {
	scp->fxp_error_message = "expected FXP_STATUS packet";
	scp->fxp_errtype = -1;
    } else {
	unsigned long ul;
	if (!sftp_pkt_getuint32(pktin, &ul)) {
	    scp->fxp_error_message = "malformed FXP_STATUS packet";
	    scp->fxp_errtype = -1;
	} else {
	    scp->fxp_errtype = ul;
	    if (scp->fxp_errtype < 0 ||
		scp->fxp_errtype >= sizeof(messages) / sizeof(*messages))
		scp->fxp_error_message = "unknown error code";
	    else
		scp->fxp_error_message = messages[scp->fxp_errtype];
	}
    }

    if (scp->fxp_errtype == SSH_FX_OK)
	return 1;
    else if (scp->fxp_errtype == SSH_FX_EOF)
	return 0;
    else
	return -1;
}

static void fxp_internal_error(char *msg, scp_tag *scp)
{
    scp->fxp_error_message = msg;
    scp->fxp_errtype = -1;
}

const char *fxp_error(scp_tag *scp)
{
    return scp->fxp_error_message;
}

int fxp_error_type(scp_tag *scp)
{
    return scp->fxp_errtype;
}


/*
 * Canonify a pathname.
 */
struct sftp_request *fxp_realpath_send(char *path, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_REALPATH);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring_start(pktout);
    sftp_pkt_addstring_str(pktout, path);
    sftp_send(pktout, scp);

    return req;
}

char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req, scp_tag *scp)
{
    memfree(req);

    if (pktin->type == SSH_FXP_NAME) {
	unsigned long count;
	char *path;
	int len;

	if (!sftp_pkt_getuint32(pktin, &count) || count != 1) {
	    fxp_internal_error("REALPATH did not return name count of 1\n", scp);
            sftp_pkt_free(pktin);
	    return NULL;
	}
	if (!sftp_pkt_getstring(pktin, &path, &len)) {
	    fxp_internal_error("REALPATH returned malformed FXP_NAME\n", scp);
            sftp_pkt_free(pktin);
	    return NULL;
	}
	path = mkstr(path, len);
	sftp_pkt_free(pktin);
	return path;
    } else {
	fxp_got_status(pktin, scp);
        sftp_pkt_free(pktin);
	return NULL;
    }
}

/*
 * Open a file.
 */
struct sftp_request *fxp_open_send(char *path, int type, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_OPEN);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring(pktout, (char*)path);
    sftp_pkt_adduint32(pktout, type);
    sftp_pkt_adduint32(pktout, 0);     /* (FIXME) empty ATTRS structure */
    sftp_send(pktout, scp);

    return req;
}

struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin,
				 struct sftp_request *req, scp_tag *scp)
{
    memfree(req);

    if (pktin->type == SSH_FXP_HANDLE) {
	char *hstring;
	struct fxp_handle *handle;
	int len;

	if (!sftp_pkt_getstring(pktin, &hstring, &len)) {
	    fxp_internal_error("OPEN returned malformed FXP_HANDLE\n", scp);
            sftp_pkt_free(pktin);
	    return NULL;
	}
	handle = memalloc(struct fxp_handle);
	handle->hstring = mkstr(hstring, len);
	handle->hlen = len;
	sftp_pkt_free(pktin);
	return handle;
    } else {
	fxp_got_status(pktin, scp);
        sftp_pkt_free(pktin);
	return NULL;
    }
}

/*
 * Open a directory.
 */
struct sftp_request *fxp_opendir_send(char *path, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_OPENDIR);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring(pktout, path);
    sftp_send(pktout, scp);

    return req;
}

struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin,
				    struct sftp_request *req, scp_tag *scp)
{
    memfree(req);
    if (pktin->type == SSH_FXP_HANDLE) {
	char *hstring;
	struct fxp_handle *handle;
	int len;

	if (!sftp_pkt_getstring(pktin, &hstring, &len)) {
	    fxp_internal_error("OPENDIR returned malformed FXP_HANDLE\n", scp);
            sftp_pkt_free(pktin);
	    return NULL;
	}
	handle = memalloc(struct fxp_handle);
	handle->hstring = mkstr(hstring, len);
	handle->hlen = len;
	sftp_pkt_free(pktin);
	return handle;
    } else {
	fxp_got_status(pktin, scp);
        sftp_pkt_free(pktin);
	return NULL;
    }
}

/*
 * Close a file/dir.
 */
struct sftp_request *fxp_close_send(struct fxp_handle *handle, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_CLOSE);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring_start(pktout);
    sftp_pkt_addstringdata(pktout, handle->hstring, handle->hlen);
    sftp_send(pktout, scp);

    memfree(handle->hstring);
    memfree(handle);

    return req;
}

void fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req, scp_tag *scp)
{
    memfree(req);
    fxp_got_status(pktin, scp);
    sftp_pkt_free(pktin);
}

struct sftp_request *fxp_mkdir_send(char *path, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_MKDIR);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring(pktout, (char*)path);
    sftp_pkt_adduint32(pktout, 0);     /* (FIXME) empty ATTRS structure */
    sftp_send(pktout, scp);

    return req;
}

int fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req, scp_tag *scp)
{
    int id;
    memfree(req);
    id = fxp_got_status(pktin, scp);
    sftp_pkt_free(pktin);
    if (id != 1) {
    	return 0;
    }
    return 1;
}

struct sftp_request *fxp_rmdir_send(char *path, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_RMDIR);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring(pktout, path);
    sftp_send(pktout, scp);

    return req;
}

int fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req, scp_tag *scp)
{
    int id;
    memfree(req);
    id = fxp_got_status(pktin, scp);
    sftp_pkt_free(pktin);
    if (id != 1) {
    	return 0;
    }
    return 1;
}

struct sftp_request *fxp_remove_send(char *fname, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_REMOVE);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring(pktout, fname);
    sftp_send(pktout, scp);

    return req;
}

int fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req, scp_tag *scp)
{
    int id;
    memfree(req);
    id = fxp_got_status(pktin, scp);
    sftp_pkt_free(pktin);
    if (id != 1) {
    	return 0;
    }
    return 1;
}

struct sftp_request *fxp_rename_send(char *srcfname, char *dstfname, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_RENAME);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring(pktout, srcfname);
    sftp_pkt_addstring(pktout, dstfname);
    sftp_send(pktout, scp);

    return req;
}

int fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req, scp_tag *scp)
{
    int id;
    memfree(req);
    id = fxp_got_status(pktin, scp);
    sftp_pkt_free(pktin);
    if (id != 1) {
    	return 0;
    }
    return 1;
}

/*
 * Retrieve the attributes of a file. We have fxp_stat which works
 * on filenames, and fxp_fstat which works on open file handles.
 */
struct sftp_request *fxp_stat_send(char *fname, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_STAT);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring(pktout, (char*)fname);
    sftp_send(pktout, scp);

    return req;
}

int fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req,
		  struct fxp_attrs *attrs, scp_tag *scp)
{
    memfree(req);
    if (pktin->type == SSH_FXP_ATTRS) {
	if (!sftp_pkt_getattrs(pktin, attrs)) {
	    fxp_internal_error("malformed SSH_FXP_ATTRS packet", scp);
	    sftp_pkt_free(pktin);
	    return 0;
	}
	sftp_pkt_free(pktin);
	return 1;
    } else {
	fxp_got_status(pktin, scp);
        sftp_pkt_free(pktin);
	return 0;
    }
}

struct sftp_request *fxp_fstat_send(struct fxp_handle *handle, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_FSTAT);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring_start(pktout);
    sftp_pkt_addstringdata(pktout, handle->hstring, handle->hlen);
    sftp_send(pktout, scp);

    return req;
}

int fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req,
		   struct fxp_attrs *attrs, scp_tag *scp)
{
    memfree(req);
    if (pktin->type == SSH_FXP_ATTRS) {
	if (!sftp_pkt_getattrs(pktin, attrs)) {
	    fxp_internal_error("malformed SSH_FXP_ATTRS packet", scp);
	    sftp_pkt_free(pktin);
	    return 0;
	}
	sftp_pkt_free(pktin);
	return 1;
    } else {
	fxp_got_status(pktin, scp);
        sftp_pkt_free(pktin);
	return 0;
    }
}

/*
 * Set the attributes of a file.
 */
struct sftp_request *fxp_setstat_send(char *fname, struct fxp_attrs attrs, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_SETSTAT);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring(pktout, fname);
    sftp_pkt_addattrs(pktout, attrs);
    sftp_send(pktout, scp);

    return req;
}

int fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req, scp_tag *scp)
{
    int id;
    memfree(req);
    id = fxp_got_status(pktin, scp);
    sftp_pkt_free(pktin);
    if (id != 1) {
    	return 0;
    }
    return 1;
}

struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle,
				       struct fxp_attrs attrs, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_FSETSTAT);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring_start(pktout);
    sftp_pkt_addstringdata(pktout, handle->hstring, handle->hlen);
    sftp_pkt_addattrs(pktout, attrs);
    sftp_send(pktout, scp);

    return req;
}

int fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req, scp_tag *scp)
{
    int id;
    memfree(req);
    id = fxp_got_status(pktin, scp);
    sftp_pkt_free(pktin);
    if (id != 1) {
    	return 0;
    }
    return 1;
}

/*
 * Read from a file. Returns the number of bytes read, or -1 on an
 * error, or possibly 0 if EOF. (I'm not entirely sure whether it
 * will return 0 on EOF, or return -1 and store SSH_FX_EOF in the
 * error indicator. It might even depend on the SFTP server.)
 */
struct sftp_request *fxp_read_send(struct fxp_handle *handle,
				   uint64 offset, int len, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_READ);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring_start(pktout);
    sftp_pkt_addstringdata(pktout, handle->hstring, handle->hlen);
    sftp_pkt_adduint64(pktout, offset);
    sftp_pkt_adduint32(pktout, len);
    sftp_send(pktout, scp);

    return req;
}

int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req,
		  char *buffer, int len, scp_tag *scp)
{
    memfree(req);
    if (pktin->type == SSH_FXP_DATA) {
	char *str;
	int rlen;

	if (!sftp_pkt_getstring(pktin, &str, &rlen)) {
	    fxp_internal_error("READ returned malformed SSH_FXPdata packet", scp);
            sftp_pkt_free(pktin);
	    return -1;
	}

	if (rlen > len || rlen < 0) {
	    fxp_internal_error("READ returned more bytes than requested", scp);
            sftp_pkt_free(pktin);
	    return -1;
	}

	memcpy(buffer, str, rlen);
        sftp_pkt_free(pktin);
	return rlen;
    } else {
	fxp_got_status(pktin, scp);
        sftp_pkt_free(pktin);
	return -1;
    }
}

/*
 * Read from a directory.
 */
struct sftp_request *fxp_readdir_send(struct fxp_handle *handle, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_READDIR);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring_start(pktout);
    sftp_pkt_addstringdata(pktout, handle->hstring, handle->hlen);
    sftp_send(pktout, scp);

    return req;
}

struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin,
				   struct sftp_request *req, scp_tag *scp)
{
    memfree(req);
    if (pktin->type == SSH_FXP_NAME) {
	struct fxp_names *ret;
	unsigned long i;

	/*
	 * Sanity-check the number of names. Minimum is obviously
	 * zero. Maximum is the remaining space in the packet
	 * divided by the very minimum length of a name, which is
	 * 12 bytes (4 for an empty filename, 4 for an empty
	 * longname, 4 for a set of attribute flags indicating that
	 * no other attributes are supplied).
	 */
	if (!sftp_pkt_getuint32(pktin, &i) ||
	    i > (pktin->length-pktin->savedpos)/12) {
	    fxp_internal_error("malformed FXP_NAME packet", scp);
	    sftp_pkt_free(pktin);
	    return NULL;
	}

	/*
	 * Ensure the implicit multiplication in the memallocn() call
	 * doesn't suffer integer overflow and cause us to malloc
	 * too little space.
	 */
	if (i > INT_MAX / sizeof(struct fxp_name)) {
	    fxp_internal_error("unreasonably large FXP_NAME packet", scp);
	    sftp_pkt_free(pktin);
	    return NULL;
	}

	ret = memalloc(struct fxp_names);
	ret->nnames = i;
	ret->names = memallocn(ret->nnames, struct fxp_name);
	for (i = 0; i < (unsigned long)ret->nnames; i++) {
	    char *str1, *str2;
	    int len1, len2;
	    if (!sftp_pkt_getstring(pktin, &str1, &len1) ||
		!sftp_pkt_getstring(pktin, &str2, &len2) ||
		!sftp_pkt_getattrs(pktin, &ret->names[i].attrs)) {
		fxp_internal_error("malformed FXP_NAME packet", scp);
		while (i--) {
		    memfree(ret->names[i].filename);
		    memfree(ret->names[i].longname);
		}
		memfree(ret->names);
		memfree(ret);
		memfree(pktin);
		return NULL;
	    }
	    ret->names[i].filename = mkstr(str1, len1);
	    ret->names[i].longname = mkstr(str2, len2);
	}
        sftp_pkt_free(pktin);
	return ret;
    } else {
	fxp_got_status(pktin, scp);
        sftp_pkt_free(pktin);
	return NULL;
    }
}

/*
 * Write to a file. Returns 0 on error, 1 on OK.
 */
struct sftp_request *fxp_write_send(struct fxp_handle *handle,
				    char *buffer, uint64 offset, int len, scp_tag *scp)
{
    struct sftp_request *req = sftp_alloc_request();
    struct sftp_packet *pktout;

    pktout = sftp_pkt_init(SSH_FXP_WRITE);
    sftp_pkt_adduint32(pktout, req->id);
    sftp_pkt_addstring_start(pktout);
    sftp_pkt_addstringdata(pktout, handle->hstring, handle->hlen);
    sftp_pkt_adduint64(pktout, offset);
    sftp_pkt_addstring_start(pktout);
    sftp_pkt_addstringdata(pktout, buffer, len);
    sftp_send(pktout, scp);

    return req;
}

int fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req, scp_tag *scp)
{
    memfree(req);
    fxp_got_status(pktin, scp);
    sftp_pkt_free(pktin);
    return scp->fxp_errtype == SSH_FX_OK;
}

/*
 * Free up an fxp_names structure.
 */
void fxp_free_names(struct fxp_names *names)
{
    int i;

    for (i = 0; i < names->nnames; i++) {
	memfree(names->names[i].filename);
	memfree(names->names[i].longname);
    }
    memfree(names->names);
    memfree(names);
}

/*
 * Duplicate an fxp_name structure.
 */
struct fxp_name *fxp_dup_name(struct fxp_name *name)
{
    struct fxp_name *ret;
    ret = memalloc(struct fxp_name);
    ret->filename = (char*)dupstr((char*)name->filename);
    ret->longname = (char*)dupstr((char*)name->longname);
    ret->attrs = name->attrs;	       /* structure copy */
    return ret;
}

/*
 * Free up an fxp_name structure.
 */
void fxp_free_name(struct fxp_name *name)
{
    memfree(name->filename);
    memfree(name->longname);
    memfree(name);
}

/*
 * Store user data in an sftp_request structure.
 */
void *fxp_get_userdata(struct sftp_request *req)
{
    return req->userdata;
}

void fxp_set_userdata(struct sftp_request *req, void *data)
{
    req->userdata = data;
}

/*
 * A wrapper to go round fxp_read_* and fxp_write_*, which manages
 * the queueing of multiple read/write requests.
 */

struct req {
    char *buffer;
    int len, retlen, complete;
    uint64 offset;
    struct req *next, *prev;
};

struct fxp_xfer {
    uint64 offset, furthestdata, filesize;
    int req_totalsize, req_maxsize, eof, err;
    struct fxp_handle *fh;
    struct req *head, *tail;
};

static struct fxp_xfer *xfer_init(struct fxp_handle *fh, uint64 offset)
{
    struct fxp_xfer *xfer = memalloc(struct fxp_xfer);

    xfer->fh = fh;
    xfer->offset = offset;
    xfer->head = xfer->tail = NULL;
    xfer->req_totalsize = 0;
    xfer->req_maxsize = 1048576;
    xfer->err = 0;
    xfer->filesize = uint64_make(ULONG_MAX, ULONG_MAX);
    xfer->furthestdata = uint64_make(0, 0);

    return xfer;
}

int xfer_done(struct fxp_xfer *xfer)
{
    /*
     * We're finished if we've seen EOF _and_ there are no
     * outstanding requests.
     */
    return (xfer->eof || xfer->err) && !xfer->head;
}

void xfer_download_queue(struct fxp_xfer *xfer, scp_tag *scp)
{
    while (xfer->req_totalsize < xfer->req_maxsize &&
	   !xfer->eof && !xfer->err) {
	/*
	 * Queue a new read request.
	 */
	struct req *rr;
	struct sftp_request *req;

	rr = memalloc(struct req);
	rr->offset = xfer->offset;
	rr->complete = 0;
	if (xfer->tail) {
	    xfer->tail->next = rr;
	    rr->prev = xfer->tail;
	} else {
	    xfer->head = rr;
	    rr->prev = NULL;
	}
	xfer->tail = rr;
	rr->next = NULL;

	rr->len = 32617;
	rr->buffer = memallocn(rr->len, char);
	sftp_register(req = fxp_read_send(xfer->fh, rr->offset, rr->len, scp));
	fxp_set_userdata(req, rr);

	xfer->offset = uint64_add32(xfer->offset, rr->len);
	xfer->req_totalsize += rr->len;

#ifdef DEBUG_DOWNLOAD
	{ char buf[40]; uint64_decimal(rr->offset, buf); printf("queueing read request %p at %s\n", rr, buf); }
#endif
    }
}

struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64 offset, scp_tag *scp)
{
    struct fxp_xfer *xfer = xfer_init(fh, offset);

    xfer->eof = FALSE;
    xfer_download_queue(xfer, scp);

    return xfer;
}

int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin, scp_tag *scp)
{
    struct sftp_request *rreq;
    struct req *rr;

    rreq = sftp_find_request(pktin, scp);
    rr = (struct req *)fxp_get_userdata(rreq);
    if (!rr)
	return 0;		       /* this packet isn't ours */
    rr->retlen = fxp_read_recv(pktin, rreq, rr->buffer, rr->len, scp);
#ifdef DEBUG_DOWNLOAD
    printf("read request %p has returned [%d]\n", rr, rr->retlen);
#endif

    if ((rr->retlen < 0 && fxp_error_type(scp)==SSH_FX_EOF) || rr->retlen == 0) {
	xfer->eof = TRUE;
	rr->complete = -1;
#ifdef DEBUG_DOWNLOAD
	printf("setting eof\n");
#endif
    } else if (rr->retlen < 0) {
	/* some error other than EOF; signal it back to caller */
	xfer_set_error(xfer);
	rr->complete = -1;
	return -1;
    }

    rr->complete = 1;

    /*
     * Special case: if we have received fewer bytes than we
     * actually read, we should do something. For the moment I'll
     * just throw an ersatz FXP error to signal this; the SFTP
     * draft I've got says that it can't happen except on special
     * files, in which case seeking probably has very little
     * meaning and so queueing an additional read request to fill
     * up the gap sounds like the wrong answer. I'm not sure what I
     * should be doing here - if it _was_ a special file, I suspect
     * I simply shouldn't have been queueing multiple requests in
     * the first place...
     */
    if (rr->retlen > 0 && uint64_compare(xfer->furthestdata, rr->offset) < 0) {
	xfer->furthestdata = rr->offset;
#ifdef DEBUG_DOWNLOAD
	{ char buf[40];
	uint64_decimal(xfer->furthestdata, buf);
	printf("setting furthestdata = %s\n", buf); }
#endif
    }

    if (rr->retlen < rr->len) {
	uint64 filesize = uint64_add32(rr->offset,
				       (rr->retlen < 0 ? 0 : rr->retlen));
#ifdef DEBUG_DOWNLOAD
	{ char buf[40];
	uint64_decimal(filesize, buf);
	printf("short block! trying filesize = %s\n", buf); }
#endif
	if (uint64_compare(xfer->filesize, filesize) > 0) {
	    xfer->filesize = filesize;
#ifdef DEBUG_DOWNLOAD
	    printf("actually changing filesize\n");
#endif	    
	}
    }

    if (uint64_compare(xfer->furthestdata, xfer->filesize) > 0) {
	scp->fxp_error_message = "received a short buffer from FXP_READ, but not"
	    " at EOF";
	scp->fxp_errtype = -1;
	xfer_set_error(xfer);
	return -1;
    }

    return 1;
}

void xfer_set_error(struct fxp_xfer *xfer)
{
    xfer->err = 1;
}

int xfer_downloaddata(struct fxp_xfer *xfer, void **buf, int *len)
{
    void *retbuf = NULL;
    int retlen = 0;

    /*
     * Discard anything at the head of the rr queue with complete <
     * 0; return the first thing with complete > 0.
     */
    while (xfer->head && xfer->head->complete && !retbuf) {
	struct req *rr = xfer->head;

	if (rr->complete > 0) {
	    retbuf = rr->buffer;
	    retlen = rr->retlen;
#ifdef DEBUG_DOWNLOAD
	    printf("handing back data from read request %p\n", rr);
#endif
	}
#ifdef DEBUG_DOWNLOAD
	else
	    printf("skipping failed read request %p\n", rr);
#endif

	xfer->head = xfer->head->next;
	if (xfer->head)
	    xfer->head->prev = NULL;
	else
	    xfer->tail = NULL;
	xfer->req_totalsize -= rr->len;
	memfree(rr);
    }

    if (retbuf) {
	*buf = retbuf;
	*len = retlen;
	return 1;
    } else
	return 0;
}

struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64 offset)
{
    struct fxp_xfer *xfer = xfer_init(fh, offset);

    /*
     * We set `eof' to 1 because this will cause xfer_done() to
     * return true iff there are no outstanding requests. During an
     * upload, our caller will be responsible for working out
     * whether all the data has been sent, so all it needs to know
     * from us is whether the outstanding requests have been
     * handled once that's done.
     */
    xfer->eof = 1;

    return xfer;
}

int xfer_upload_ready(struct fxp_xfer *xfer)
{
    if (xfer->req_totalsize < xfer->req_maxsize)
	return 1;
    else
	return 0;
}

void xfer_uploaddata(struct fxp_xfer *xfer, char *buffer, int len, scp_tag *scp)
{
    struct req *rr;
    struct sftp_request *req;

    rr = memalloc(struct req);
    rr->offset = xfer->offset;
    rr->complete = 0;
    if (xfer->tail) {
	xfer->tail->next = rr;
	rr->prev = xfer->tail;
    } else {
	xfer->head = rr;
	rr->prev = NULL;
    }
    xfer->tail = rr;
    rr->next = NULL;

    rr->len = len;
    rr->buffer = NULL;
    sftp_register(req = fxp_write_send(xfer->fh, buffer, rr->offset, len, scp));
    fxp_set_userdata(req, rr);

    xfer->offset = uint64_add32(xfer->offset, rr->len);
    xfer->req_totalsize += rr->len;

#ifdef DEBUG_UPLOAD
    { char buf[40]; uint64_decimal(rr->offset, buf); printf("queueing write request %p at %s [len %d]\n", rr, buf, len); }
#endif
}

int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin, scp_tag *scp)
{
    struct sftp_request *rreq;
    struct req *rr, *prev, *next;
    int ret;

    rreq = sftp_find_request(pktin, scp);
    rr = (struct req *)fxp_get_userdata(rreq);
    if (!rr)
	return 0;		       /* this packet isn't ours */
    ret = fxp_write_recv(pktin, rreq, scp);
#ifdef DEBUG_UPLOAD
    printf("write request %p has returned [%d]\n", rr, ret);
#endif

    /*
     * Remove this one from the queue.
     */
    prev = rr->prev;
    next = rr->next;
    if (prev)
	prev->next = next;
    else
	xfer->head = next;
    if (next)
	next->prev = prev;
    else
	xfer->tail = prev;
    xfer->req_totalsize -= rr->len;
    memfree(rr);

    if (!ret)
	return -1;

    return 1;
}

void xfer_cleanup(struct fxp_xfer *xfer)
{
    struct req *rr;
    while (xfer->head) {
	rr = xfer->head;
	xfer->head = xfer->head->next;
	memfree(rr->buffer);
	memfree(rr);
    }
    memfree(xfer);
}

/* deprecated function */
static void ssh_scp_recv(char *buf, int len, scp_tag *scp)
{
    scp->outptr = buf;
    scp->outlen = len;
}


int sftp_senddata(char *buf, int len, scp_tag *scp)
{
	scp->ssh->protocol(scp->ssh,buf, len, 0, scp->channel_localid);
    return 1;
}


int file_type(wchar_t *name)
{
    DWORD attr;
    attr = GetFileAttributes(name);
    /* We know of no `weird' files under Windows. */
    if (attr == (DWORD)-1)
	return FILE_TYPE_NONEXISTENT;
    else if (attr & FILE_ATTRIBUTE_DIRECTORY)
	return FILE_TYPE_DIRECTORY;
    else
	return FILE_TYPE_FILE;
}


/*
 *  Recursively send the contents of a directory.
 */
char *scp_save_remotepath(scp_tag *scp)
{
    if (scp->using_sftp)
	return scp->scp_sftp_remotepath;
    else
	return NULL;
}

DirHandle *open_directory(wchar_t *name, scp_tag *scp)
{
    HANDLE h;
    WIN32_FIND_DATA fdat;
    wchar_t *findfile;
    DirHandle *ret;

    /* Enumerate files in dir `foo'. */
    findfile = dupscat(name, L"/*", NULL);
    h = FindFirstFile(findfile, &fdat);
    if (h == INVALID_HANDLE_VALUE)
	return NULL;
    memfree(findfile);

    ret = memalloc(DirHandle);
    ret->h = h;
    ret->name = dupsstr(fdat.cFileName);
	ret->next=scp->chunk.dir;
    return ret;
}
wchar_t *read_filename(DirHandle *dir)
{
    do {

	if (!dir->name) {
	    WIN32_FIND_DATA fdat;
	    int ok = FindNextFile(dir->h, &fdat);
	    if (!ok)
		return NULL;
	    else
		dir->name = dupsstr(fdat.cFileName);
	}

	assert(dir->name);
	if (dir->name[0] == '.' &&
	    (dir->name[1] == '\0' ||
	     (dir->name[1] == '.' && dir->name[2] == '\0'))) {
	    memfree(dir->name);
	    dir->name = NULL;
	}

    } while (!dir->name);

    if (dir->name) {
	wchar_t *ret = dir->name;
	dir->name = NULL;
	return ret;
    } else
	return NULL;
}

static void bump(scp_tag *scp, char *fmt, ...)
{
    char *str, *str2;
    va_list ap;
    va_start(ap, fmt);
    str = dupvprintf(fmt, ap);
    va_end(ap);
    str2 = dupcat(str, "\n", NULL);
    memfree(str);

    if (back != NULL && back->connected(scp->ssh)) {
	char ch;
	ssh_special_ovr(scp->ssh, TS_EOF, scp->channel_localid);
	ssh_scp_recv((char *) &ch, 1, scp);
    }
	scp->closes=TRUE;
	if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, str2, strlen(str2));
    memfree(str2);
}

/*
 *  Wait for a response from the other side.
 *  Return 0 if ok, -1 if error.
 */
static int response(scp_tag *scp)
{
    char ch, resp, rbuf[2048];
    int p;

    ssh_scp_recv((char *) &resp, 1, scp);

    p = 0;
    switch (resp) {
      case 0:			       /* ok */
	return (0);
      default:
	rbuf[p++] = resp;
	/* fallthrough */
      case 1:			       /* error */
      case 2:			       /* fatal error */
	do {
	    ssh_scp_recv((char *) &ch, 1, scp);
	    rbuf[p++] = ch;
	} while (p < sizeof(rbuf) && ch != '\n');
	rbuf[p - 1] = '\0';
	if (resp == 1)
		if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, rbuf, strlen(rbuf));
	else
	    bump(scp,"%s", rbuf);
	return (-1);
    }
}

DirHandle* close_directory(DirHandle *dir, scp_tag *scp)
{
	DirHandle *dir_old;
	dir_old=dir->next;
    FindClose(dir->h);
    if (dir->name)
	memfree(dir->name);
    memfree(dir);
	return dir_old;
}

int scp_send_enddir(scp_tag *scp)
{
    if (scp->using_sftp) {
	memfree(scp->scp_sftp_remotepath);
	scp->scp_sftp_remotepath=NULL;
	return 0;
    } else {
		scp->ssh->protocol(scp->ssh, "E\n", 2,  0, scp->channel_localid);
	return response(scp);
    }
}

void scp_restore_remotepath(scp_tag *scp)
{
	scp_tag::chunk_tag::save_target_tag *data_old;
	data_old=scp->chunk.save_target_ptr->next;
	memfree(scp->chunk.save_target_ptr);
	scp->chunk.save_target_ptr=data_old;
    if (scp->using_sftp && scp->chunk.save_target_ptr)
		scp->scp_sftp_remotepath = scp->chunk.save_target_ptr->save_target;
}


WFile *open_existing_file(wchar_t *name, uint64 *size,
			  unsigned long *mtime, unsigned long *atime)
{
    HANDLE h;
    WFile *ret;

    h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL,
		   OPEN_EXISTING, 0, 0);
    if (h == INVALID_HANDLE_VALUE)
	return NULL;

    ret = memalloc(WFile);
    ret->h = h;

    if (size)
        size->lo=GetFileSize(h, &(size->hi));

    if (mtime || atime) {
	FILETIME actime, wrtime;
	GetFileTime(h, NULL, &actime, &wrtime);
	if (atime)
	    TIME_WIN_TO_POSIX(actime, *atime);
	if (mtime)
	    TIME_WIN_TO_POSIX(wrtime, *mtime);
    }

    return ret;
}

int scp_send_filetimes(unsigned long mtime, unsigned long atime, scp_tag *scp)
{
    if (scp->using_sftp) {
	scp->scp_sftp_mtime = mtime;
	scp->scp_sftp_atime = atime;
	scp->scp_has_times = 1;
	return 0;
    } else {
	char buf[80];
	sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
	scp->ssh->protocol(scp->ssh, buf, strlen(buf),  0, scp->channel_localid);
	return response(scp);
    }
}


#define MAX_SCP_BUFSIZE 16384


void close_rfile(WFile *f)
{
    CloseHandle(f->h);
    memfree(f);
}


int read_from_file(WFile *f, void *buffer, int length)
{
    DWORD ret, read;
    ret = ReadFile(f->h, buffer, length, &read, NULL);
    if (!ret)
	return -1;		       /* error */
    else
	return read;
}

int test_wildcard(wchar_t *name, int cmdline)
{
    HANDLE fh;
    WIN32_FIND_DATA fdat;

    /* First see if the exact name exists. */
    if (GetFileAttributes(name) != (DWORD)-1)
	return WCTYPE_FILENAME;

    /* Otherwise see if a wildcard match finds anything. */
    fh = FindFirstFile(name, &fdat);
    if (fh == INVALID_HANDLE_VALUE)
	return WCTYPE_NONEXISTENT;

    FindClose(fh);
    return WCTYPE_WILDCARD;
}

int scp_send_errmsg(char *str, scp_tag *scp)
{
    if (scp->using_sftp) {
	/* do nothing; we never need to send our errors to the server */
    } else {
	scp->ssh->protocol(scp->ssh, "\001", 1,  0, scp->channel_localid);/* scp protocol error prefix */
	scp->ssh->protocol(scp->ssh, str, strlen(str),  0, scp->channel_localid);
    }
    return 0;			       /* can't fail */
}

/*
 * Return a pointer to the portion of str that comes after the last
 * slash (or backslash or colon, if `local' is TRUE).
 */
wchar_t *stripslashes(wchar_t *str, int local)
{
    wchar_t *p;

    if (local) {
        p = wcschr(str, L':');
        if (p) str = p+1;
    }

    p = wcsrchr(str, L'/');
    if (p) str = p+1;

    if (local) {
	p = wcsrchr(str, L'\\');
	if (p) str = p+1;
    }

    return str;
}

char *stripslashes(char *str, int local)
{
    char *p;

    if (local) {
        p = strchr(str, ':');
        if (p) str = p+1;
    }

    p = strrchr(str, '/');
    if (p) str = p+1;

    if (local) {
	p = strrchr(str, '\\');
	if (p) str = p+1;
    }

    return str;
}

WildcardMatcher *begin_wildcard_matching(wchar_t *name)
{
    HANDLE h;
    WIN32_FIND_DATA fdat;
    WildcardMatcher *ret;
    wchar_t *last;

    h = FindFirstFile(name, &fdat);
    if (h == INVALID_HANDLE_VALUE)
	return NULL;

    ret = memalloc(WildcardMatcher);
    ret->h = h;
    ret->srcpath = dupsstr(name);
    last = stripslashes(ret->srcpath, 1);
    *last = '\0';
    if (fdat.cFileName[0] == '.' &&
	(fdat.cFileName[1] == '\0' ||
	 (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))
	ret->name = NULL;
    else
	ret->name = dupscat(ret->srcpath, fdat.cFileName, NULL);

    return ret;
}
wchar_t *wildcard_get_filename(WildcardMatcher *dir)
{
    while (!dir->name) {
	WIN32_FIND_DATA fdat;
	int ok = FindNextFile(dir->h, &fdat);

	if (!ok)
	    return NULL;

	if (fdat.cFileName[0] == '.' &&
	    (fdat.cFileName[1] == '\0' ||
	     (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))
	    dir->name = NULL;
	else
	    dir->name = dupscat(dir->srcpath, fdat.cFileName, NULL);
    }

    if (dir->name) {
	wchar_t *ret = dir->name;
	dir->name = NULL;
	return ret;
    } else
	return NULL;
}

void finish_wildcard_matching(WildcardMatcher *dir)
{
    FindClose(dir->h);
    if (dir->name)
	memfree(dir->name);
    memfree(dir->srcpath);
    memfree(dir);
}

void dir_cleanup(scp_tag *scp) {
	while ((scp->chunk.dir=close_directory(scp->chunk.dir,scp))!=NULL);
}

void rp_cleanup(scp_tag *scp) {
	while (scp->chunk.save_target_ptr) {
		if (scp->scp_sftp_remotepath) memfree(scp->scp_sftp_remotepath);
		scp_restore_remotepath(scp);
	}
	if (scp->scp_sftp_remotepath) memfree(scp->scp_sftp_remotepath);
}

/* upload files */
void  toremote(scp_tag *scp)
{

	char *p;
	scp->chunk.wrerror = 0;
	scp->chunk.type_wait=SCP_UPLOAD;
	scp->chunk.pkt_wait=0;

	crBegin(scp->chunk.crstate);
	scp->chunk.targisdir = 0;
    struct sftp_packet *pktout, *pktin;
    unsigned long remotever;
	memset(&scp->chunk,0, sizeof(scp->chunk));
	scp->scp_sftp_remotepath=NULL;

	if (!scp->initialized) {

		pktout = sftp_pkt_init(SSH_FXP_INIT);
		sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
		sftp_send(pktout, scp);
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		if (pktin->type != SSH_FXP_VERSION) {
		fxp_internal_error("did not receive FXP_VERSION", scp);
			sftp_pkt_free(pktin);
			return;
		}
		if (!sftp_pkt_getuint32(pktin, &remotever)) {
		fxp_internal_error("malformed FXP_VERSION packet", scp);
			sftp_pkt_free(pktin);
		return;
		}
		if (remotever > SFTP_PROTO_VERSION) {
		fxp_internal_error
			("remote protocol is more advanced than we support", scp);
			sftp_pkt_free(pktin);
		return;
		}
		/*
		  * In principle, this packet might also contain extension-
		  * string pairs. We should work through them and look for any
		  * we recognise. In practice we don't currently do so because
		  * we know we don't recognise _any_.
		  */
		sftp_pkt_free(pktin);
		scp->initialized=TRUE;
	}
    if (scp->using_sftp) {

	struct sftp_request *req, *rreq;
	int ret;

	sftp_register(req = fxp_stat_send(scp->remote, scp));
	scp->chunk.pkt_wait=1;
	crReturnV;
	pktin=scp->chunk.pkt;
	rreq = sftp_find_request(pktin, scp);
	ret = fxp_stat_recv(pktin, rreq, &scp->chunk.attrs,scp);
	
	if (!ret || !(scp->chunk.attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS))
	    scp->scp_sftp_targetisdir = 0;
	else
	    scp->scp_sftp_targetisdir = (scp->chunk.attrs.permissions & 0040000) != 0;

	if (scp->targetshouldbedirectory && !scp->scp_sftp_targetisdir) {
		p=dupprintf("pscp: remote filespec %s: not a directory\n", scp->remote);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
		memfree(p);
		return;
	}
	scp->scp_has_times = 0;
} else {
	(void) response(scp);
    }
	int wc_type;wc_type = test_wildcard(scp->local, 1);
	if (wc_type == WCTYPE_NONEXISTENT) {
		p=dupprintf("%s: No such file or directory", scp->local);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
		memfree(p);	
		return;
	} else if (wc_type == WCTYPE_FILENAME) {
	    //source(scp->local, scp);
		scp->chunk.middle=dupsstr(scp->local);
snap:
		wchar_t *last;
		int attr;
		time_t stat_starttime, stat_lasttime;

		attr = file_type(scp->chunk.middle);
		if (attr == FILE_TYPE_NONEXISTENT ||
		attr == FILE_TYPE_WEIRD) {
		p=dupprintf("%S: %s file or directory", scp->chunk.middle,
		(attr == FILE_TYPE_WEIRD ? "Not a" : "No such"));
bfailure:
		if (scp->scp_sftp_remotepath) memfree(scp->scp_sftp_remotepath);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		if (scp->chunk.dir) dir_cleanup(scp);
		rp_cleanup(scp);
		memfree(scp->chunk.middle);
		if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
		memfree(p);	
		return;
		}

		if (attr == FILE_TYPE_DIRECTORY) {
		if (scp->recursive) {

			if (scp->scp_sftp_remotepath) 	{
				scp->scp_sftp_remotepath = (char*)dupcat((char*)scp->scp_sftp_remotepath, "/", scp->chunk.fname, NULL);
				memfree(scp->chunk.fname);
			}	else 
				scp->scp_sftp_remotepath = (char*)dupstr((char*)scp->remote);


			/*
			  * Avoid . and .. directories.
			  */
			wchar_t *pi;
			pi = wcsrchr(scp->chunk.middle, L'/');
			if (!pi)
				pi = wcsrchr(scp->chunk.middle, L'\\');
			if (!pi)
				pi = scp->chunk.middle;
			else
			pi++;
			if (!wcscmp(pi, L".") || !wcscmp(pi, L".."))
			/* skip . and .. */ ;
			else {

				/* maybe send filetime */

				struct scp_tag::chunk_tag::save_target_tag *d;
				d=scp->chunk.save_target_ptr;

				scp->chunk.save_target_ptr=memalloc(struct scp_tag::chunk_tag::save_target_tag);
				scp->chunk.save_target_ptr->save_target = scp_save_remotepath(scp);
				scp->chunk.save_target_ptr->next=d;

				if (scp->using_sftp) {
				struct sftp_packet *pktin;
				struct sftp_request *req, *rreq;
				int ret;

				scp->chunk.fname = (char*)dupstr((char*)scp->scp_sftp_remotepath);

				/*
				 * We don't worry about whether we managed to create the
				 * directory, because if it exists already it's OK just to
				 * use it. Instead, we will stat it afterwards, and if it
				 * exists and is a directory we will assume we were either
				 * successful or it didn't matter.
				 */
				sftp_register(req = fxp_mkdir_send(scp->chunk.fname, scp));
				scp->chunk.pkt_wait=1;
				crReturnV;
				pktin=scp->chunk.pkt;
				rreq = sftp_find_request(pktin, scp);
				ret = fxp_mkdir_recv(pktin, rreq, scp);

				if (!ret)
					scp->chunk.fullname = (char*)fxp_error(scp);
				else
					scp->chunk.fullname = (char*)"server reported no error";

				sftp_register(req = fxp_stat_send(scp->chunk.fname, scp));
				scp->chunk.pkt_wait=1;
				crReturnV;
				pktin=scp->chunk.pkt;
				rreq = sftp_find_request(pktin, scp);
				ret = fxp_stat_recv(pktin, rreq, &scp->chunk.attrs, scp);

				if (!ret || !(scp->chunk.attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) ||
					!(scp->chunk.attrs.permissions & 0040000)) {
					p=dupprintf("unable to create directory %s: %s",
						scp->chunk.fname, scp->chunk.fullname);
					goto bfailure;
				}

			} else {
				char buf[40];
				sprintf(buf, "D%04o 0 ", 0755);
				scp->ssh->protocol(scp->ssh, buf, strlen(buf),  0, scp->channel_localid);
				scp->ssh->protocol(scp->ssh, (char*)scp->chunk.fname, strlen((char*)scp->chunk.fname),  0, scp->channel_localid);
				scp->ssh->protocol(scp->ssh, "\n", 1,  0, scp->channel_localid);
				scp->chunk.crstate=0;
				scp->chunk.type_wait=0;
				response(scp);
			}


				scp->chunk.dir = open_directory(scp->chunk.middle,scp);
				wchar_t *filename;
				if (scp->chunk.dir != NULL) {
				while ((filename = read_filename(scp->chunk.dir)) != NULL) {
					wchar_t *foundfile = dupscat(scp->chunk.middle, L"/", filename, NULL);
					memfree(scp->chunk.middle);
					scp->chunk.middle=foundfile;
					scp->chunk.fname=(char*)dupprintf("%S",filename);
					memfree(filename);
					scp->scp_sftp_targetisdir=1;
					goto snap;
nosnap:
					if (scp->chunk.fname) {
						memfree(scp->chunk.fname);
						scp->chunk.fname=NULL;
					}
					wchar_t *p=wcsrchr(scp->chunk.middle,L'/');
					if (p) *p=L'\0';
					filename=dupsstr(scp->chunk.middle);
					memfree(scp->chunk.middle);
					scp->chunk.middle=filename;
nosnapstart:
					;
				}
				}
				scp->chunk.dir=close_directory(scp->chunk.dir, scp);

				(void) scp_send_enddir(scp);

				scp_restore_remotepath(scp);
				wchar_t *p=wcsrchr(scp->chunk.middle,L'/');
				if (p) *p=L'\0';
				filename=dupsstr(scp->chunk.middle);
				memfree(scp->chunk.middle);
				scp->chunk.middle=filename;
			}
		} else {
			p=dupprintf("%S: not a regular file", scp->chunk.middle);
			goto bfailure;
		}
		if (scp->chunk.dir) goto nosnapstart; else {
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
			return;
		}
		}
		if ((last = wcsrchr(scp->chunk.middle, L'/')) == NULL)
			last = scp->chunk.middle;
		else
		last++;
		if (wcsrchr(last, L'\\') != NULL)
		last = wcsrchr(last, L'\\') + 1;
		if (last == scp->chunk.middle && wcschr(scp->chunk.middle, L':') != NULL)
			last = wcschr(scp->chunk.middle, L':') + 1;

		scp->chunk.f = open_existing_file(scp->chunk.middle, &scp->chunk.size, &scp->scp_sftp_mtime, &scp->scp_sftp_atime);
		if (scp->chunk.f == NULL) {
			p=dupprintf("%S: Cannot open file", scp->chunk.middle);
			goto bfailure;
		}
		if (scp->preserve) {
			if (scp_send_filetimes(scp->scp_sftp_mtime, scp->scp_sftp_atime, scp)) {
				memfree(scp->scp_sftp_remotepath );
				if (scp->chunk.dir) goto nosnap; else if (scp->chunk.filename) goto endwc; else {
					scp->chunk.crstate=0;
					scp->chunk.type_wait=0;
					return;
				}
			}
		if (scp->using_sftp) {
			struct sftp_packet *pktin;
			struct sftp_request *req, *rreq;
			if (scp->chunk.fname) memfree(scp->chunk.fname);
			scp->chunk.fname=(char *)dupprintf("%S",last);

			if (scp->scp_sftp_targetisdir) {
				scp->chunk.fullname = (char *)dupcat((char*)scp->scp_sftp_remotepath, "/", scp->chunk.fname, NULL);
			} else {
				if (scp->scp_sftp_remotepath) scp->chunk.fullname = (char*)dupstr((char*)scp->scp_sftp_remotepath); else 
					scp->chunk.fullname= (char *)dupstr((char*)scp->remote);
			}

			sftp_register(req = fxp_open_send(scp->chunk.fullname, SSH_FXF_WRITE |
							  SSH_FXF_CREAT | SSH_FXF_TRUNC, scp));
			scp->chunk.pkt_wait=1;
			crReturnV;
			pktin=scp->chunk.pkt;
			rreq = sftp_find_request(pktin, scp);
			scp->scp_sftp_filehandle = fxp_open_recv(pktin, rreq, scp);

			if (!scp->scp_sftp_filehandle) {
				p=dupprintf("pscp: unable to open %s: %s",
					  scp->chunk.fullname, fxp_error(scp));
				goto bfailure;
			}
			scp->scp_sftp_fileoffset = uint64_make(0, 0);
			scp->scp_sftp_xfer = xfer_upload_init(scp->scp_sftp_filehandle,
							 scp->scp_sftp_fileoffset);
			} else {
			char buf[40];
			char sizestr[40];
			uint64_decimal(scp->chunk.size, sizestr);
			sprintf(buf, "C%04o %s ", 0644, sizestr);
			scp->ssh->protocol(scp->ssh, buf, strlen(buf),  0, scp->channel_localid);
			scp->chunk.fname=(char*)dupprintf("%S",last);
			scp->ssh->protocol(scp->ssh, scp->chunk.fname, strlen((char*)scp->chunk.fname),  0, scp->channel_localid);
			scp->ssh->protocol(scp->ssh, "\n", 1,  0, scp->channel_localid);
			memfree(scp->chunk.fname);
				scp->chunk.crstate=0;
				scp->chunk.type_wait=0;
				response(scp);
			}
		}	

		stat_starttime = time(NULL);
		stat_lasttime = 0;

		for (scp->chunk.i = uint64_make(0,0);
		 uint64_compare(scp->chunk.i,scp->chunk.size) < 0;
		 scp->chunk.i = uint64_add32(scp->chunk.i,4096)) {
		char transbuf[4096];
		int j;
		scp->chunk.k = 4096;

		if (uint64_compare(uint64_add32(scp->chunk.i, scp->chunk.k),scp->chunk.size) > 0) /* i + k > size */ 
			scp->chunk.k = (uint64_subtract(scp->chunk.size, scp->chunk.i)).lo; 	/* k = size - i; */
		if ((j = read_from_file(scp->chunk.f, transbuf, scp->chunk.k)) != scp->chunk.k) {
			memfree(scp->scp_sftp_remotepath );
			wchar_t *z=scp->chunk.middle;
			bump(scp,"%S: Read error", z);
			memfree(z);
			return;
		}
		if (scp->using_sftp) {
		int ret;
		struct sftp_packet *pktin;

		if (!scp->scp_sftp_filehandle) {
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
			memfree(scp->scp_sftp_remotepath );
			if (scp->chunk.dir) dir_cleanup(scp);
			rp_cleanup(scp);
			wchar_t *t=scp->chunk.middle;
			bump(scp, "%S: Network error occurred", t);
			memfree(t);
			return;
		}

		while (!xfer_upload_ready(scp->scp_sftp_xfer)) {
			scp->chunk.pkt_wait=1;
			crReturnV;
			pktin=scp->chunk.pkt;
			ret = xfer_upload_gotpkt(scp->scp_sftp_xfer, pktin, scp);
			sftp_pkt_free(pktin);
			if (!ret) {
			p=dupprintf("error while writing: %s\n", fxp_error(scp));
			goto bfailure;
		    }
		}

		xfer_uploaddata(scp->scp_sftp_xfer, transbuf, scp->chunk.k, scp);

		scp->scp_sftp_fileoffset = uint64_add32(scp->scp_sftp_fileoffset, scp->chunk.k);
		} else {
			scp->ssh->protocol(scp->ssh, transbuf, scp->chunk.k, 0, scp->channel_localid);

		}
		}
		close_rfile(scp->chunk.f);
		if (scp->using_sftp) {
		struct sftp_packet *pktin;
		struct sftp_request *req, *rreq;
		int ret;

		while (!xfer_done(scp->scp_sftp_xfer)) {
			scp->chunk.pkt_wait=1;
			crReturnV;
			pktin=scp->chunk.pkt;
			xfer_upload_gotpkt(scp->scp_sftp_xfer, pktin, scp);
			sftp_pkt_free(pktin);
		}
		xfer_cleanup(scp->scp_sftp_xfer);

		if (!scp->scp_sftp_filehandle) {
			p=dupprintf("error while uploading: %s\n", fxp_error(scp));
			goto bfailure;
		}
		if (scp->scp_has_times) {
			scp->chunk.attrs.flags = SSH_FILEXFER_ATTR_ACMODTIME;
			scp->chunk.attrs.atime = scp->scp_sftp_atime;
			scp->chunk.attrs.mtime = scp->scp_sftp_mtime;
			sftp_register(req = fxp_fsetstat_send(scp->scp_sftp_filehandle,scp->chunk.attrs,scp));
			scp->chunk.pkt_wait=1;
			crReturnV;
			pktin=scp->chunk.pkt;
			rreq = sftp_find_request(pktin, scp);
			ret = fxp_fsetstat_recv(pktin, rreq, scp);
			if (!ret) {
			p=dupprintf("unable to set file times: %s\n", fxp_error(scp));
			goto bfailure;
			}
		}
		sftp_register(req = fxp_close_send(scp->scp_sftp_filehandle, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		rreq = sftp_find_request(pktin, scp);
		fxp_close_recv(pktin, rreq, scp);
		scp->scp_has_times = 0;
		} else {
			scp->ssh->protocol(scp->ssh, "", 1,  0, scp->channel_localid);
			response(scp);
		}
		if (scp->chunk.dir) goto nosnap; else if (scp->chunk.filename) goto endwc;
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		notify_threadqueue(scp->ssh,SCP_CALLBACK_COMPLETED,0,scp->channel_localid,NULL,0);
		return;
		} else {

		scp->chunk.wc = begin_wildcard_matching(scp->local);
	    if (scp->chunk.wc == NULL) {
			p=dupprintf("%S: No such file or directory", scp->chunk.middle);
			goto bfailure;
	    }
		scp->scp_sftp_remotepath = dupstr(scp->remote);

	    while ((scp->chunk.filename = wildcard_get_filename(scp->chunk.wc)) != NULL) {
			memfree(scp->chunk.middle);
			scp->chunk.middle=scp->chunk.filename;
		goto snap;
endwc:
		;
	    }
		memfree(scp->chunk.filename);
		memfree(scp->scp_sftp_remotepath );
	    finish_wildcard_matching(scp->chunk.wc);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		notify_threadqueue(scp->ssh,SCP_CALLBACK_COMPLETED,0,scp->channel_localid,NULL,0);
		return;
	}
	crFinishV;
}

/*
 * Another utility routine that translates a non-wildcard string
 * into its raw equivalent by removing any escaping backslashes.
 * Expects a target string buffer of anything up to the length of
 * the original wildcard. You can also pass NULL as the output
 * buffer if you're only interested in the return value.
 * 
 * Returns 1 on success, or 0 if a wildcard character was
 * encountered. In the latter case the output string MAY not be
 * zero-terminated and you should not use it for anything!
 */
int wc_unescape(wchar_t *output, const wchar_t *wildcard)
{
    while (*wildcard) {
	if (*wildcard == L'\\') {
	    wildcard++;
	    /* We are lenient about trailing backslashes in non-wildcards. */
	    if (*wildcard) {
		if (output)
		    *output++ = *wildcard;
		wildcard++;
	    }
	} else if (*wildcard == L'*' || *wildcard == L'?' ||
		   *wildcard == L'[' || *wildcard == L']') {
	    return 0;		       /* it's a wildcard! */
	} else {
	    if (output)
		*output++ = *wildcard;
	    wildcard++;
	}
    }
    *output = L'\0';
    return 1;			       /* it's clean */
}

int wc_unescape(char *output, const char *wildcard)
{
    while (*wildcard) {
	if (*wildcard == '\\') {
	    wildcard++;
	    /* We are lenient about trailing backslashes in non-wildcards. */
	    if (*wildcard) {
		if (output)
		    *output++ = *wildcard;
		wildcard++;
	    }
	} else if (*wildcard == '*' || *wildcard == '?' ||
		   *wildcard == '[' || *wildcard == ']') {
	    return 0;		       /* it's a wildcard! */
	} else {
	    if (output)
		*output++ = *wildcard;
	    wildcard++;
	}
    }
    *output = '\0';
    return 1;			       /* it's clean */
}

int scp_sink_init(scp_tag *scp)
{
    if (!scp->using_sftp) {
	scp->ssh->protocol(scp->ssh, "", 1,  0, scp->channel_localid);
    }
    return 0;
}
/*
 * Determine whether a string is entirely composed of dots.
 */
static int is_dots(wchar_t *str)
{
    return str[wcsspn(str, L".")] == L'\0';
}
static int is_dots(char *str)
{
    return str[strspn(str, ".")] == '\0';
}

int vet_filename(char *name)
{
    if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':'))
	return FALSE;

    if (!name[strspn(name, ".")])      /* entirely composed of dots */
	return FALSE;

    return TRUE;
}


char *dir_file_cat(char *dir, char *file)
{
    return dupcat(dir, "\\", file, NULL);
}

char *dir_file_cat(char *dir, wchar_t *file)
{
    return dupprintf("%s\\%S", dir,file);
}
wchar_t *dir_file_cat(wchar_t *dir, char *file)
{
    return dupsprintf(L"%s\\%S", dir,file);
}
int create_directory(wchar_t *name)
{
    return CreateDirectory(name, NULL) != 0;
}

WFile *open_new_file(wchar_t *name)
{
    HANDLE h;
    WFile *ret;

    h = CreateFile(name, GENERIC_WRITE, 0, NULL,
		   CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if (h == INVALID_HANDLE_VALUE)
	return NULL;

    ret = memalloc(WFile);
    ret->h = h;

    return ret;
}


int write_to_file(WFile *f, void *buffer, int length)
{
    DWORD ret, written;
    ret = WriteFile(f->h, buffer, length, &written, NULL);
    if (!ret)
	return -1;		       /* error */
    else
	return written;
}

void set_file_times(WFile *f, unsigned long mtime, unsigned long atime)
{
    FILETIME actime, wrtime;
    TIME_POSIX_TO_WIN(atime, actime);
    TIME_POSIX_TO_WIN(mtime, wrtime);
    SetFileTime(f->h, NULL, &actime, &wrtime);
}
void close_wfile(WFile *f)
{
    CloseHandle(f->h);
    memfree(f);
}

void  getstat(scp_tag *scp)
{
	scp->chunk.wrerror = 0;
	scp->chunk.type_wait=SCP_FSTAT;
	scp->chunk.pkt_wait=0;

	crBegin(scp->chunk.crstate);

	scp->chunk.targisdir = 0;
    struct sftp_packet *pktout, *pktin;
    unsigned long remotever;
	scp->chunk.middle=NULL;
	scp->chunk.dir=NULL;
	scp->chunk.save_target_ptr=NULL;
	scp->chunk.filename=NULL;

	if (!scp->initialized) {

		pktout = sftp_pkt_init(SSH_FXP_INIT);
		sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
		sftp_send(pktout, scp);
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		if (pktin->type != SSH_FXP_VERSION) {
		fxp_internal_error("did not receive FXP_VERSION", scp);
			sftp_pkt_free(pktin);
			return;
		}
		if (!sftp_pkt_getuint32(pktin, &remotever)) {
		fxp_internal_error("malformed FXP_VERSION packet", scp);
			sftp_pkt_free(pktin);
		return;
		}
		if (remotever > SFTP_PROTO_VERSION) {
		fxp_internal_error
			("remote protocol is more advanced than we support", scp);
			sftp_pkt_free(pktin);
		return;
		}
		/*
		  * In principle, this packet might also contain extension-
		  * string pairs. We should work through them and look for any
		  * we recognise. In practice we don't currently do so because
		  * we know we don't recognise _any_.
		  */
		sftp_pkt_free(pktin);
		scp->initialized=TRUE;
	}
    if (scp->using_sftp) {

	struct sftp_request *req, *rreq;
	int ret;

	sftp_register(req = fxp_stat_send(scp->remote, scp));
	scp->chunk.pkt_wait=1;
	crReturnV;
	pktin=scp->chunk.pkt;
	rreq = sftp_find_request(pktin, scp);
	ret = fxp_stat_recv(pktin, rreq, &scp->chunk.attrs,scp);
	if (!ret) {
		char *p=dupprintf("scp: cannot access file %s: %s",
			  scp->remote, fxp_error(scp));
		if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
		memfree(p);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;

	} else {
		sftp_register(req = fxp_realpath_send(scp->remote, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		rreq = sftp_find_request(pktin, scp);
		scp->chunk.fname=fxp_realpath_recv(pktin, rreq, scp);

		struct scp_attrs *attr=memalloc(scp_attrs);
		strcpy(attr->name,(char*)scp->chunk.fname);
		memcpy(&attr->flags,&scp->chunk.attrs, sizeof(fxp_attrs));
		memfree(scp->chunk.fname);

		notify_threadqueue(scp->ssh, SCP_STAT_INDEX, 0, scp->channel_localid, (const char*)attr,sizeof(struct scp_attrs));
		memfree(attr);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		notify_threadqueue(scp->ssh,SCP_CALLBACK_COMPLETED,0,scp->channel_localid,NULL,0);
		return;
	}
	}
	crFinishV;
}


void  setstat(scp_tag *scp)
{
	scp->chunk.wrerror = 0;
	scp->chunk.type_wait=SCP_SET_STAT;
	scp->chunk.pkt_wait=0;
	struct scp_attrs *attr;
	attr = (struct scp_attrs *)scp->remote;

	crBegin(scp->chunk.crstate);

	scp->chunk.targisdir = 0;
    struct sftp_packet *pktout, *pktin;
    unsigned long remotever;
	scp->chunk.middle=NULL;
	scp->chunk.dir=NULL;
	scp->chunk.save_target_ptr=NULL;
	scp->chunk.filename=NULL;

	if (!scp->initialized) {

		pktout = sftp_pkt_init(SSH_FXP_INIT);
		sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
		sftp_send(pktout, scp);
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		if (pktin->type != SSH_FXP_VERSION) {
		fxp_internal_error("did not receive FXP_VERSION", scp);
			sftp_pkt_free(pktin);
			return;
		}
		if (!sftp_pkt_getuint32(pktin, &remotever)) {
		fxp_internal_error("malformed FXP_VERSION packet", scp);
			sftp_pkt_free(pktin);
		return;
		}
		if (remotever > SFTP_PROTO_VERSION) {
		fxp_internal_error
			("remote protocol is more advanced than we support", scp);
			sftp_pkt_free(pktin);
		return;
		}
		/*
		  * In principle, this packet might also contain extension-
		  * string pairs. We should work through them and look for any
		  * we recognise. In practice we don't currently do so because
		  * we know we don't recognise _any_.
		  */
		sftp_pkt_free(pktin);
		scp->initialized=TRUE;
	}
    if (scp->using_sftp) {

	struct sftp_request *req, *rreq;
	int ret;

	sftp_register(req = fxp_stat_send(scp->remote, scp));
	scp->chunk.pkt_wait=1;
	crReturnV;
	pktin=scp->chunk.pkt;
	rreq = sftp_find_request(pktin, scp);
	ret = fxp_stat_recv(pktin, rreq, &scp->chunk.attrs,scp);
	if (!ret) {
		char *p=dupprintf("scp: cannot access file %s: %s",
			  scp->remote, fxp_error(scp));
		if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
		memfree(p);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;

	} else {
		struct fxp_attrs attrib;
		memcpy(&attrib,&attr->flags,sizeof(fxp_attrs));
		sftp_register(req = fxp_setstat_send(scp->remote,attrib,scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		rreq = sftp_find_request(pktin, scp);
		ret=fxp_setstat_recv(pktin, rreq, scp);

		if (!ret) {
			char *p=dupprintf("scp: cannot set stat to file %s: %s",
				  scp->remote, fxp_error(scp));
			if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
			memfree(p);
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
		} else {
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
			notify_threadqueue(scp->ssh,SCP_CALLBACK_COMPLETED,0,scp->channel_localid,NULL,0);
			return;
		}
	}
	}
	crFinishV;
}


void  dorename(scp_tag *scp)
{
	scp->chunk.wrerror = 0;
	scp->chunk.type_wait=SCP_RENAME;
	scp->chunk.pkt_wait=0;

	crBegin(scp->chunk.crstate);

	scp->chunk.targisdir = 0;
    struct sftp_packet *pktout, *pktin;
    unsigned long remotever;
	scp->chunk.middle=NULL;
	scp->chunk.dir=NULL;
	scp->chunk.save_target_ptr=NULL;
	scp->chunk.filename=NULL;

	if (!scp->initialized) {

		pktout = sftp_pkt_init(SSH_FXP_INIT);
		sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
		sftp_send(pktout, scp);
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		if (pktin->type != SSH_FXP_VERSION) {
		fxp_internal_error("did not receive FXP_VERSION", scp);
			sftp_pkt_free(pktin);
			return;
		}
		if (!sftp_pkt_getuint32(pktin, &remotever)) {
		fxp_internal_error("malformed FXP_VERSION packet", scp);
			sftp_pkt_free(pktin);
		return;
		}
		if (remotever > SFTP_PROTO_VERSION) {
		fxp_internal_error
			("remote protocol is more advanced than we support", scp);
			sftp_pkt_free(pktin);
		return;
		}
		/*
		  * In principle, this packet might also contain extension-
		  * string pairs. We should work through them and look for any
		  * we recognise. In practice we don't currently do so because
		  * we know we don't recognise _any_.
		  */
		sftp_pkt_free(pktin);
		scp->initialized=TRUE;
	}
    if (scp->using_sftp) {

	struct sftp_request *req, *rreq;
	int ret;

	sftp_register(req = fxp_stat_send(scp->remote, scp));
	scp->chunk.pkt_wait=1;
	crReturnV;
	pktin=scp->chunk.pkt;
	rreq = sftp_find_request(pktin, scp);
	ret = fxp_stat_recv(pktin, rreq, &scp->chunk.attrs,scp);
	if (!ret) {
		char *p=dupprintf("scp: cannot access file %s: %s",
			  scp->remote, fxp_error(scp));
		if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
		memfree(p);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;

	} else {
		sftp_register(req = fxp_rename_send(scp->remote, (char*)scp->local, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		rreq = sftp_find_request(pktin, scp);
		ret = fxp_rename_recv(pktin, rreq, scp);
		if (!ret) {
			char *p=dupprintf("scp: cannot rename file %s: %s",
				  scp->remote, fxp_error(scp));
			if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
			memfree(p);
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
		} else {
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
			notify_threadqueue(scp->ssh,SCP_CALLBACK_COMPLETED,0,scp->channel_localid,NULL,0);
			return;
		}
	}
	}
	crFinishV;
}

void  mkdir(scp_tag *scp)
{
	int ret;
    unsigned long remotever;
    struct sftp_packet *pktout, *pktin;
	struct sftp_request *req, *rreq;
	scp->chunk.type_wait=SCP_MKDIR;
	scp->chunk.pkt_wait=0;

	crBegin(scp->chunk.crstate);

	if (!scp->initialized) {
		pktout = sftp_pkt_init(SSH_FXP_INIT);
		sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
		sftp_send(pktout, scp);
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		if (pktin->type != SSH_FXP_VERSION) {
		fxp_internal_error("did not receive FXP_VERSION", scp);
			sftp_pkt_free(pktin);
			return;
		}
		if (!sftp_pkt_getuint32(pktin, &remotever)) {
		fxp_internal_error("malformed FXP_VERSION packet", scp);
			sftp_pkt_free(pktin);
		return;
		}
		if (remotever > SFTP_PROTO_VERSION) {
		fxp_internal_error
			("remote protocol is more advanced than we support", scp);
			sftp_pkt_free(pktin);
		return;
		}
		/*
		  * In principle, this packet might also contain extension-
		  * string pairs. We should work through them and look for any
		  * we recognise. In practice we don't currently do so because
		  * we know we don't recognise _any_.
		  */
		sftp_pkt_free(pktin);
		scp->initialized=TRUE;
	}
    if (scp->using_sftp) {
		sftp_register(req = fxp_mkdir_send(scp->remote, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		rreq = sftp_find_request(pktin, scp);
		ret = fxp_mkdir_recv(pktin, rreq, scp);

		if (!ret) {
			char *p=dupprintf("cannot create directory %s: %s",
				  scp->remote, fxp_error(scp));
			if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
			memfree(p);
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
		} else {
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
			notify_threadqueue(scp->ssh,SCP_CALLBACK_COMPLETED,0,scp->channel_localid,NULL,0);
			return;
		}
	}
	crFinishV;

}

/*
 * Execute the sink part of the SCP protocol.
 */
void  getdir(scp_tag *scp)
{
	int ret;
    unsigned long remotever;
    struct sftp_packet *pktout, *pktin;
	struct sftp_request *req, *rreq;
	scp->chunk.type_wait=SCP_DIRLIST;
	scp->chunk.pkt_wait=0;

	crBegin(scp->chunk.crstate);

	if (!scp->initialized) {
		pktout = sftp_pkt_init(SSH_FXP_INIT);
		sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
		sftp_send(pktout, scp);
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		if (pktin->type != SSH_FXP_VERSION) {
		fxp_internal_error("did not receive FXP_VERSION", scp);
			sftp_pkt_free(pktin);
			return;
		}
		if (!sftp_pkt_getuint32(pktin, &remotever)) {
		fxp_internal_error("malformed FXP_VERSION packet", scp);
			sftp_pkt_free(pktin);
		return;
		}
		if (remotever > SFTP_PROTO_VERSION) {
		fxp_internal_error
			("remote protocol is more advanced than we support", scp);
			sftp_pkt_free(pktin);
		return;
		}
		/*
		  * In principle, this packet might also contain extension-
		  * string pairs. We should work through them and look for any
		  * we recognise. In practice we don't currently do so because
		  * we know we don't recognise _any_.
		  */
		sftp_pkt_free(pktin);
		scp->initialized=TRUE;
	}
    scp_sink_init(scp);

	sftp_register(req = fxp_stat_send(scp->remote,scp));
	scp->chunk.pkt_wait=1;
	crReturnV;
	pktin=scp->chunk.pkt;
	rreq = sftp_find_request(pktin, scp);
	ret = fxp_stat_recv(pktin, rreq, &scp->chunk.attrs, scp);

	if (!ret || !(scp->chunk.attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		return;
	}

	sftp_register(req = fxp_realpath_send(scp->remote, scp));
	scp->chunk.pkt_wait=1;
	crReturnV;
	pktin=scp->chunk.pkt;
	rreq = sftp_find_request(pktin, scp);
	scp->chunk.fname=fxp_realpath_recv(pktin, rreq, scp);


	if ((scp->chunk.attrs.permissions & 0070000) == 0040000) {
	    struct fxp_names *names;

	    sftp_register(req = fxp_opendir_send(scp->chunk.fname, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
	    rreq = sftp_find_request(pktin, scp);
	    scp->chunk.dirhandle = fxp_opendir_recv(pktin, rreq, scp);

	    if (!scp->chunk.dirhandle) {
		char *p=dupprintf("scp: unable to open directory %s: %s",
			  scp->chunk.fname, fxp_error(scp));
		if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
		memfree(p);
		if (scp->chunk.fname) memfree(scp->chunk.fname);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		return;
	    }
	    scp->chunk.nnames = scp->chunk.namesize = 0;
	    scp->chunk.ournames = NULL;
	    while (1) {
		int i;

		sftp_register(req = fxp_readdir_send(scp->chunk.dirhandle, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		rreq = sftp_find_request(pktin, scp);
		names = fxp_readdir_recv(pktin, rreq, scp);

		if (names == NULL) {
		    if (fxp_error_type(scp) == SSH_FX_EOF)
			break;
		    memfree(scp->chunk.ournames);
			char *p=dupprintf("scp: reading directory %s: %s\n",
			      scp->chunk.fname, fxp_error(scp));
			if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
			memfree(p);
			if (scp->chunk.fname) memfree(scp->chunk.fname);
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
			return;
		}
		if (names->nnames == 0) {
		    fxp_free_names(names);
		    break;
		}
		if (scp->chunk.nnames + names->nnames >= scp->chunk.namesize) {
		    scp->chunk.namesize += names->nnames + 128;
		    scp->chunk.ournames = memrealloc(scp->chunk.ournames, scp->chunk.namesize+1, struct fxp_name);
		}
		for (i = 0; i < names->nnames; i++) {
		    if (!strcmp(names->names[i].filename, ".") ||
			!strcmp(names->names[i].filename, "..")) {
			/*
			 * . and .. are normal consequences of
			 * reading a directory, and aren't worth
			 * complaining about.
			 */
			memfree(names->names[i].filename);
			memfree(names->names[i].longname);

		    } else if (!vet_filename(names->names[i].filename)) {
/*			tell_user(scp, "ignoring potentially dangerous server-"
				  "supplied filename '%S'\n",
				  names->names[i].filename);*/
				;
		    } else
			scp->chunk.ournames[scp->chunk.nnames++] = names->names[i];
		}
		names->nnames = 0;	       /* prevent free_names */
		fxp_free_names(names);
	    }
	    sftp_register(req = fxp_close_send(scp->chunk.dirhandle, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
	    rreq = sftp_find_request(pktin, scp);
	    fxp_close_recv(pktin, rreq, scp);
		scp_names *snames=memallocn(scp->chunk.nnames,struct scp_names);

		for (int i=0;i<scp->chunk.nnames;i++) {
			strcpy(snames[i].attrs.name,scp->chunk.ournames[i].filename);
			strcpy(snames[i].longname,scp->chunk.ournames[i].longname);
			memcpy(&snames[i].attrs.flags,&scp->chunk.ournames[i].attrs,sizeof(fxp_attrs));
		}

		struct fxp_names *nms=memalloc(struct fxp_names);
		nms->names=scp->chunk.ournames;
		nms->nnames=scp->chunk.nnames;
		fxp_free_names(nms);
		if (scp->chunk.fname) memfree(scp->chunk.fname);
		notify_threadqueue(scp->ssh, SCP_DIRNAMES_INDEX, 0, scp->channel_localid, (const char*)snames,scp->chunk.nnames*sizeof(struct scp_names));
		memfree(snames);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		notify_threadqueue(scp->ssh,SCP_CALLBACK_COMPLETED,0,scp->channel_localid,NULL,0);
		return;

	} else {
	    /*
	     * It's a file. Return SCP_SINK_FILE.
	     */
			char *p=dupprintf("scp: this is a file %s\n",
			      scp->chunk.fname);
			if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
			memfree(p);

		if (scp->chunk.fname) memfree(scp->chunk.fname);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		return;
	}
	crFinishV;
}

void  tolocal(scp_tag *scp)
{
	scp->chunk.wrerror = 0;
	scp->chunk.type_wait=SCP_DOWNLOAD;
	scp->chunk.pkt_wait=0;
	char *p=NULL;

	crBegin(scp->chunk.crstate);
	scp->chunk.targisdir = 0;
    struct sftp_packet *pktout, *pktin;
    unsigned long remotever;
	memset(&scp->chunk,0, sizeof(scp->chunk));
	scp->scp_sftp_remotepath=NULL;

	if (!scp->initialized) {

		pktout = sftp_pkt_init(SSH_FXP_INIT);
		sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
		sftp_send(pktout, scp);
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		if (pktin->type != SSH_FXP_VERSION) {
		fxp_internal_error("did not receive FXP_VERSION", scp);
			sftp_pkt_free(pktin);
			return;
		}
		if (!sftp_pkt_getuint32(pktin, &remotever)) {
		fxp_internal_error("malformed FXP_VERSION packet", scp);
			sftp_pkt_free(pktin);
		return;
		}
		if (remotever > SFTP_PROTO_VERSION) {
		fxp_internal_error
			("remote protocol is more advanced than we support", scp);
			sftp_pkt_free(pktin);
		return;
		}
		/*
		  * In principle, this packet might also contain extension-
		  * string pairs. We should work through them and look for any
		  * we recognise. In practice we don't currently do so because
		  * we know we don't recognise _any_.
		  */
		sftp_pkt_free(pktin);
		scp->initialized=TRUE;
	}
    if (scp->using_sftp) {
	char *newsource;

	/*
	 * It's possible that the source string we've been given
	 * contains a wildcard. If so, we must split the directory
	 * away from the wildcard itself (throwing an error if any
	 * wildcardness comes before the final slash) and arrange
	 * things so that a dirstack entry will be set up.
	 */
	newsource = memallocn(1+strlen(scp->remote), char);
	if (!wc_unescape(newsource, scp->remote)) {
	    /* Yes, here we go; it's a wildcard. Bah. */
	    char *dupsource, *lastpart, *dirpart, *wildcard;
	    dupsource = dupstr(scp->remote);
	    lastpart = stripslashes(dupsource, 0);
	    wildcard = dupstr(lastpart);
	    *lastpart = '\0';
	    if (*dupsource && dupsource[1]) {
		/*
		 * The remains of dupsource are at least two
		 * characters long, meaning the pathname wasn't
		 * empty or just `/'. Hence, we remove the trailing
		 * slash.
		 */
		lastpart[-1] = '\0';
	    } else if (!*dupsource) {
		/*
		 * The remains of dupsource are _empty_ - the whole
		 * pathname was a wildcard. Hence we need to
		 * replace it with ".".
		 */
		memfree(dupsource);
		dupsource = dupstr(".");
	    }

	    /*
	     * Now we have separated our string into dupsource (the
	     * directory part) and wildcard. Both of these will
	     * need freeing at some point. Next step is to remove
	     * wildcard escapes from the directory part, throwing
	     * an error if it contains a real wildcard.
	     */
	    dirpart = memallocn(1+strlen(dupsource), char);
	    if (!wc_unescape(dirpart, dupsource)) {
			p=dupprintf("%s: multiple-level wildcards unsupported",
				  scp->remote);

			memfree(dirpart);
			memfree(wildcard);
			memfree(dupsource);
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
			if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
			memfree(p);
			return;
	    }

	    /*
	     * Now we have dirpart (unescaped, ie a valid remote
	     * path), and wildcard (a wildcard). This will be
	     * sufficient to arrange a dirstack entry.
	     */
	    scp->scp_sftp_remotepath = dirpart;
	    scp->scp_sftp_wildcard = wildcard;
	    memfree(dupsource);
	} else {
	    scp->scp_sftp_remotepath = newsource;
	    scp->scp_sftp_wildcard = NULL;
	}
	scp->scp_sftp_preserve = scp->preserve;
	scp->scp_sftp_recursive = scp->recursive;
	scp->scp_sftp_donethistarget = 0;
	scp->scp_sftp_dirstack_head = NULL;
	}

    int exists;
    int attr;
    uint64 stat_bytes;
    time_t stat_starttime, stat_lasttime;
    wchar_t *stat_name;

    attr = file_type(scp->local);
    if (attr == FILE_TYPE_DIRECTORY)
	scp->chunk.targisdir = 1;

	if (scp->targetshouldbedirectory && !scp->chunk.targisdir) {
		p=dupprintf("%S: Not a directory", scp->local);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
		memfree(p);
		return;
	}
    scp_sink_init(scp);

	while (1) {

	if (scp->using_sftp) {
	char *fname;
	int must_free_fname;
	struct sftp_packet *pktin;
	struct sftp_request *req, *rreq;
	int ret;

	if (!scp->scp_sftp_dirstack_head) {
	    if (!scp->scp_sftp_donethistarget) {
		/*
		 * Simple case: we are only dealing with one file.
		 */
		fname = scp->scp_sftp_remotepath;
		must_free_fname = 0;
		scp->scp_sftp_donethistarget = 1;
	    } else {
		/*
		 * Even simpler case: one file _which we've done_.
		 * Return 1 (finished).
		 */
		break;
	    }
	} else {
	    /*
	     * We're now in the middle of stepping through a list
	     * of names returned from fxp_readdir(); so let's carry
	     * on.
	     */
		struct scp_tag::scp_sftp_dirstack *head = scp->scp_sftp_dirstack_head;
	    while (head->namepos < head->namelen &&
		   (is_dots(head->names[head->namepos].filename) ||
		    (head->wildcard &&
		     !wc_match(head->wildcard,
			       head->names[head->namepos].filename))))
		head->namepos++;       /* skip . and .. */
	    if (head->namepos < head->namelen) {
		head->matched_something = 1;
		fname = dupcat(head->dirpath, "/",
			       head->names[head->namepos++].filename,
			       NULL);
		must_free_fname = 1;
	    } else {
		/*
		 * We've come to the end of the list; pop it off
		 * the stack and return an ENDDIR action (or RETRY
		 * if this was a wildcard match).
		 */
		if (head->wildcard) {
		    scp->chunk.act.action = SCP_SINK_RETRY;
		    if (!head->matched_something) {
				memfree(head->wildcard);
				memfree(head->dirpath);
				memfree(head->names);
				scp->scp_sftp_dirstack_head = head->next;
				memfree(head);
				goto locdone;

		    }
		    memfree(head->wildcard);

		} else {
		    scp->chunk.act.action = SCP_SINK_ENDDIR;
		}
		memfree(head->dirpath);
		memfree(head->names);
		scp->scp_sftp_dirstack_head = head->next;
		memfree(head);
		goto act_continue;
	    }
	}

	/*
	 * Now we have a filename. Stat it, and see if it's a file
	 * or a directory.
	 */
	scp->chunk.fname=fname;
	sftp_register(req = fxp_stat_send(fname,scp));
	scp->chunk.pkt_wait=1;
	crReturnV;
	pktin=scp->chunk.pkt;
	rreq = sftp_find_request(pktin, scp);
	ret = fxp_stat_recv(pktin, rreq, &scp->chunk.attrs, scp);

	if (!ret || !(scp->chunk.attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
		p=dupprintf("unable to identify %s: %s", scp->scp_sftp_remotepath,
		      ret ? "file type not supplied" : fxp_error(scp));
lfailure:
		if (scp->chunk.must_free_fname) memfree(scp->chunk.fname);
		memfree(scp->chunk.middle);
		if (scp->chunk.act.buf) memfree(scp->chunk.act.buf);
		if (scp->destfname) memfree(scp->destfname);
		struct scp_tag::scp_sftp_dirstack *head = scp->scp_sftp_dirstack_head;
		while (scp->scp_sftp_dirstack_head) {
			memfree(head->dirpath);
			memfree(head->names);
			scp->scp_sftp_dirstack_head = head->next;
			memfree(head);
			head = scp->scp_sftp_dirstack_head;
		}
		if (scp->scp_sftp_remotepath) memfree(scp->scp_sftp_remotepath);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
		memfree(p);
		return;
	}

	if (scp->chunk.attrs.permissions & 0040000) {
		struct scp_tag::scp_sftp_dirstack *newitem;
	    struct fxp_names *names;


		/*
	     * It's a directory. If we're not in recursive mode,
	     * this merits a complaint (which is fatal if the name
	     * was specified directly, but not if it was matched by
	     * a wildcard).
	     * 
	     * We skip this complaint completely if
	     * scp_sftp_wildcard is set, because that's an
	     * indication that we're not actually supposed to
	     * _recursively_ transfer the dir, just scan it for
	     * things matching the wildcard.
	     */
	    if (!scp->scp_sftp_recursive && !scp->scp_sftp_wildcard) {
			p=dupprintf("pscp: %s: is a directory", scp->chunk.fname);
			goto lfailure;
		}

	    /*
	     * Otherwise, the fun begins. We must fxp_opendir() the
	     * directory, slurp the filenames into memory, return
	     * SCP_SINK_DIR (unless this is a wildcard match), and
	     * set targetisdir. The next time we're called, we will
	     * run through the list of filenames one by one,
	     * matching them against a wildcard if present.
	     * 
	     * If targetisdir is _already_ set (meaning we're
	     * already in the middle of going through another such
	     * list), we must push the other (target,namelist) pair
	     * on a stack.
	     */
	    sftp_register(req = fxp_opendir_send(scp->chunk.fname, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
	    rreq = sftp_find_request(pktin, scp);
	    scp->chunk.dirhandle = fxp_opendir_recv(pktin, rreq, scp);

	    if (!scp->chunk.dirhandle) {
		p=dupprintf("scp: unable to open directory %s: %s",
			  scp->chunk.fname, fxp_error(scp));
			goto lfailure;
	    }
	    scp->chunk.nnames = scp->chunk.namesize = 0;
	    scp->chunk.ournames = NULL;
		while (1) {
		int i;

		sftp_register(req = fxp_readdir_send(scp->chunk.dirhandle, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		rreq = sftp_find_request(pktin, scp);
		names = fxp_readdir_recv(pktin, rreq, scp);

		if (names == NULL) {
		    if (fxp_error_type(scp) == SSH_FX_EOF)
			break;
			p=dupprintf("scp: reading directory %s: %s\n",
					  scp->chunk.fname, fxp_error(scp));
			goto lfailure;
		}
		if (names->nnames == 0) {
		    fxp_free_names(names);
		    break;
		}
		if (scp->chunk.nnames + names->nnames >= scp->chunk.namesize) {
		    scp->chunk.namesize += names->nnames + 128;
		    scp->chunk.ournames = memrealloc(scp->chunk.ournames, scp->chunk.namesize, struct fxp_name);
		}
		for (i = 0; i < names->nnames; i++) {
		    if (!strcmp(names->names[i].filename, ".") ||
			!strcmp(names->names[i].filename, "..")) {
			/*
			 * . and .. are normal consequences of
			 * reading a directory, and aren't worth
			 * complaining about.
			 */
		    } else if (!vet_filename(names->names[i].filename)) {
/*			tell_user(scp, "ignoring potentially dangerous server-"
				  "supplied filename '%S'\n",
				  names->names[i].filename);*/
				;
		    } else
			scp->chunk.ournames[scp->chunk.nnames++] = names->names[i];
		}
		names->nnames = 0;	       /* prevent free_names */
		fxp_free_names(names);
	    }
	    sftp_register(req = fxp_close_send(scp->chunk.dirhandle, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
	    rreq = sftp_find_request(pktin, scp);
	    fxp_close_recv(pktin, rreq, scp);

		newitem = memalloc(struct scp_tag::scp_sftp_dirstack);
	    newitem->next = scp->scp_sftp_dirstack_head;
	    newitem->names = scp->chunk.ournames;
	    newitem->namepos = 0;
	    newitem->namelen = scp->chunk.nnames;
		if (scp->chunk.must_free_fname)
		newitem->dirpath = scp->chunk.fname;
	    else
		newitem->dirpath = dupstr(scp->chunk.fname);
	    if (scp->scp_sftp_wildcard) {
		newitem->wildcard = scp->scp_sftp_wildcard;
		newitem->matched_something = 0;
		scp->scp_sftp_wildcard = NULL;
	    } else {
		newitem->wildcard = NULL;
	    }
	    scp->scp_sftp_dirstack_head = newitem;

	    if (newitem->wildcard) {
		scp->chunk.act.action = SCP_SINK_RETRY;
	    } else {
		scp->chunk.act.action = SCP_SINK_DIR;
		scp->chunk.act.buf = dupstr(stripslashes(scp->chunk.fname, 0));
		scp->chunk.act.name = scp->chunk.act.buf;
		scp->chunk.act.size = uint64_make(0,0);     /* duhh, it's a directory */
		scp->chunk.act.mode = 07777 & scp->chunk.attrs.permissions;
		if (scp->scp_sftp_preserve &&
		    (scp->chunk.attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
		    scp->chunk.act.atime = scp->chunk.attrs.atime;
		    scp->chunk.act.mtime = scp->chunk.attrs.mtime;
		    scp->chunk.act.settime = 1;
		} else
		    scp->chunk.act.settime = 0;
	    }
		goto act_continue;

	} else {

		scp_attrs *attrib;attrib=memalloc(struct scp_attrs);
		strcpy(attrib->name,scp->chunk.fname);
		memcpy(&attrib->flags,&scp->chunk.attrs.flags,sizeof(fxp_attrs));
		notify_threadqueue(scp->ssh, SCP_STAT_INDEX, 0, scp->channel_localid, (const char*)attrib,sizeof(struct scp_attrs));
		memfree(attrib);

		/*
	     * It's a file. Return SCP_SINK_FILE.
	     */
	    scp->chunk.act.action = SCP_SINK_FILE;
	    scp->chunk.act.buf = dupstr(stripslashes(scp->chunk.fname, 0));
	    scp->chunk.act.name = scp->chunk.act.buf;
	    if (scp->chunk.attrs.flags & SSH_FILEXFER_ATTR_SIZE) {
		scp->chunk.act.size = scp->chunk.attrs.size;
	    } else
		scp->chunk.act.size = uint64_make(ULONG_MAX,ULONG_MAX);   /* no idea */
	    scp->chunk.act.mode = 07777 & scp->chunk.attrs.permissions;
	    if (scp->scp_sftp_preserve &&
		(scp->chunk.attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
		scp->chunk.act.atime = scp->chunk.attrs.atime;
		scp->chunk.act.mtime = scp->chunk.attrs.mtime;
		scp->chunk.act.settime = 1;
	    } else
		scp->chunk.act.settime = 0;
		if (scp->chunk.must_free_fname)
		scp->scp_sftp_currentname = scp->chunk.fname;
	    else
		scp->scp_sftp_currentname = dupstr(scp->chunk.fname);
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		goto act_continue;
	}

	} else {
		/* sorry, no support of non-sftp transfer here */
	}
act_continue:
	if (scp->chunk.act.action == SCP_SINK_ENDDIR)
	{
		wchar_t *p;p=stripslashes(scp->chunk.middle,1);
		if (p!=scp->chunk.middle) {
			*(p-1)=L'\0';
			p=dupsstr(scp->chunk.middle);
			memfree(scp->chunk.middle);
			scp->chunk.middle=p;
			continue;
		} else {
locdone:
			if (scp->chunk.must_free_fname) memfree(scp->chunk.fname);
			if (scp->chunk.middle) memfree(scp->chunk.middle);
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
			memfree(scp->scp_sftp_remotepath);
			notify_threadqueue(scp->ssh,SCP_CALLBACK_COMPLETED,0,scp->channel_localid,NULL,0);
			return;
		}
	}

	if (scp->chunk.act.action == SCP_SINK_RETRY)
	    continue;

	if (scp->chunk.targisdir) {
	    /*
	     * Prevent the remote side from maliciously writing to
	     * files outside the target area by sending a filename
	     * containing `../'. In fact, it shouldn't be sending
	     * filenames with any slashes or colons in at all; so
	     * we'll find the last slash, backslash or colon in the
	     * filename and use only the part after that. (And
	     * warn!)
	     * 
	     * In addition, we also ensure here that if we're
	     * copying a single file and the target is a directory
	     * (common usage: `pscp host:filename .') the remote
	     * can't send us a _different_ file name. We can
	     * distinguish this case because `src' will be non-NULL
	     * and the last component of that will fail to match
	     * (the last component of) the name sent.
	     * 
	     * Well, not always; if `src' is a wildcard, we do
	     * expect to get back filenames that don't correspond
	     * exactly to it. Ideally in this case, we would like
	     * to ensure that the returned filename actually
	     * matches the wildcard pattern - but one of SCP's
	     * protocol infelicities is that wildcard matching is
	     * done at the server end _by the server's rules_ and
	     * so in general this is infeasible. Hence, we only
	     * accept filenames that don't correspond to `src' if
	     * unsafe mode is enabled or we are using SFTP (which
	     * resolves remote wildcards on the client side and can
	     * be trusted).
	     */
	    char *striptarget, *stripsrc;

	    striptarget = stripslashes(scp->chunk.act.name, 1);
/*	    if (striptarget != act.name) {
		tell_user(scp, "warning: remote host sent a compound"
			  " pathname '%s'", act.name);
		tell_user(scp, "         renaming local file to '%s'",
                          striptarget);
	    }*/

	    /*
	     * Also check to see if the target filename is '.' or
	     * '..', or indeed '...' and so on because Windows
	     * appears to interpret those like '..'.
	     */
	    if (is_dots(striptarget)) {
			bump(scp, "security violation: remote host attempted to write to"
		     " a '.' or '..' path!");
			return;
	    }

		if (scp->remote) {
		stripsrc = stripslashes(scp->remote, 1);
		if (strcmp(striptarget, stripsrc) &&
			!scp->using_sftp && !scp->scp_unsafe_mode) {
/*		    tell_user(data, "warning: remote host tried to write "
			      "to a file called '%s'", striptarget);
		    tell_user(data, "         when we requested a file "
			      "called '%s'.", stripsrc);
		    tell_user(data, "         If this is a wildcard, "
			      "consider upgrading to SSH-2 or using");
		    tell_user(stderr, "         the '-unsafe' option. Renaming"
			      " of this file has been disallowed.");*/
		    /* Override the name the server provided with our own. */
		    striptarget = stripsrc;
		}
	    }
	    if (scp->remote[0] != L'\0')
		{if (scp->chunk.middle) scp->destfname = dir_file_cat(scp->chunk.middle, striptarget); else scp->destfname = dir_file_cat(scp->local, striptarget);}
	    else
		scp->destfname = dupsprintf(L"%S",striptarget);
	} else {
	    /*
	     * In this branch of the if, the target area is a
	     * single file with an explicitly specified name in any
	     * case, so there's no danger.
	     */
	    scp->destfname = dupsstr(scp->local);
	}
	attr = file_type(scp->destfname);
	exists = (attr != FILE_TYPE_NONEXISTENT);

	if (scp->chunk.act.action == SCP_SINK_DIR) {
		scp->chunk.targisdir=1;
	    if (exists && attr != FILE_TYPE_DIRECTORY) {
		p=dupprintf("%s: Not a directory", scp->destfname);
		goto lfailure;
	    }
	    if (!exists) {
		if (!create_directory(scp->destfname)) {
			p=dupprintf("%s: Cannot create directory", scp->destfname);
			goto lfailure;
		}
	    }
		scp->chunk.middle = dupsstr(scp->destfname);
	    /* can we set the timestamp for directories ? */
	    continue;
	}

	scp->chunk.f = open_new_file(scp->destfname);
	if (scp->chunk.f == NULL) {
		p=dupprintf("%s: Cannot create file", scp->destfname);
		goto lfailure;
	}

	if (scp->using_sftp) {
	struct sftp_packet *pktin;
	struct sftp_request *req, *rreq;

	sftp_register(req = fxp_open_send(scp->scp_sftp_currentname, SSH_FXF_READ,scp));
	scp->chunk.pkt_wait=1;
	crReturnV;
	pktin=scp->chunk.pkt;
	rreq = sftp_find_request(pktin, scp);
	scp->scp_sftp_filehandle = fxp_open_recv(pktin, rreq, scp);

	if (!scp->scp_sftp_filehandle) {
		p=dupprintf("pscp: unable to open %s: %s",
		      scp->scp_sftp_currentname, fxp_error(scp));
			goto lfailure;
	}
	scp->scp_sftp_fileoffset = uint64_make(0, 0);
	scp->scp_sftp_xfer = xfer_download_init(scp->scp_sftp_filehandle,
					   scp->scp_sftp_fileoffset, scp);
	memfree(scp->scp_sftp_currentname);
    } else {
		scp->ssh->protocol(scp->ssh, "", 1,  0, scp->channel_localid);
    }
	stat_bytes = uint64_make(0, 0);
	stat_starttime = time(NULL);
	stat_lasttime = 0;
	stat_name = stripslashes(scp->destfname, 1);

	scp->chunk.i = uint64_make(0, 0);
	while (uint64_compare(scp->chunk.i,scp->chunk.act.size) < 0) {
	    char transbuf[32768];
	    int read;
	    scp->chunk.blksize = uint64_make(0, 32617);
	    if (uint64_compare(scp->chunk.blksize,uint64_subtract(scp->chunk.act.size,scp->chunk.i)) > 0)
	      scp->chunk.blksize = uint64_subtract(scp->chunk.act.size,scp->chunk.i);


		if (scp->using_sftp) {
		//struct sftp_packet *pktin;
		int ret, actuallen;
		void *vbuf;

		xfer_download_queue(scp->scp_sftp_xfer, scp);
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		ret = xfer_download_gotpkt(scp->scp_sftp_xfer, pktin, scp);
		sftp_pkt_free(pktin);

		if (ret < 0) {
			p=dupprintf("pscp: error while reading: %s", fxp_error(scp));
			goto lfailure;
		}

		if (xfer_downloaddata(scp->scp_sftp_xfer, &vbuf, &actuallen)) {
			/*
			  * This assertion relies on the fact that the natural
			  * block size used in the xfer manager is at most that
			  * used in this module. I don't like crossing layers in
			  * this way, but it'll do for now.
			  */
			assert(actuallen <= (int)scp->chunk.blksize.lo);
			memcpy(transbuf, vbuf, actuallen);
			memfree(vbuf);
		} else
			actuallen = 0;

		scp->scp_sftp_fileoffset = uint64_add32(scp->scp_sftp_fileoffset, actuallen);

		notify_threadqueue(scp->ssh, SCP_FDATA_INDEX, 0, scp->channel_localid, scp->chunk.fname, actuallen);

		read=actuallen;
		} else {
			ssh_scp_recv((char *) transbuf, (int)scp->chunk.blksize.lo,scp);
			read=0;
		}

	if (read <= 0)
	{bump(scp,"Lost connection");return;}
	if (scp->chunk.wrerror)
		continue;
		if (write_to_file(scp->chunk.f, transbuf, read) != (int)read) {
		scp->chunk.wrerror = 1;
		/* FIXME: in sftp we can actually abort the transfer */
		continue;
	    }
	    scp->chunk.i = uint64_add32(scp->chunk.i, read);
	}
	if (scp->chunk.act.settime) {
	    set_file_times(scp->chunk.f, scp->chunk.act.mtime, scp->chunk.act.atime);
	}

	close_wfile(scp->chunk.f);
	if (scp->chunk.wrerror) {
		p=dupprintf("%s: Write error", scp->destfname);
		goto lfailure;
	}
    if (scp->using_sftp) {
	struct sftp_packet *pktin;
	struct sftp_request *req, *rreq;

	/*
	 * Ensure that xfer_done() will work correctly, so we can
	 * clean up any outstanding requests from the file
	 * transfer.
	 */
	xfer_set_error(scp->scp_sftp_xfer);
	while (!xfer_done(scp->scp_sftp_xfer)) {
	    void *vbuf;
	    int len;

		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
	    xfer_download_gotpkt(scp->scp_sftp_xfer, pktin, scp);
		sftp_pkt_free(pktin);
	    if (xfer_downloaddata(scp->scp_sftp_xfer, &vbuf, &len))
		memfree(vbuf);
	}
	xfer_cleanup(scp->scp_sftp_xfer);

	sftp_register(req = fxp_close_send(scp->scp_sftp_filehandle, scp));
	scp->chunk.pkt_wait=1;
	crReturnV;
	pktin=scp->chunk.pkt;
	rreq = sftp_find_request(pktin, scp);
	fxp_close_recv(pktin, rreq, scp);
	} else {
		scp->ssh->protocol(scp->ssh, "", 1, 0, scp->channel_localid);
		response(scp);
    }
	goto locdone;
    }
	crFinishV;
}


void  dodelete(scp_tag *scp)
{
	scp->chunk.wrerror = 0;
	scp->chunk.type_wait=SCP_DELETE;
	scp->chunk.pkt_wait=0;
	struct sftp_request *req, *rreq;
	char *p=NULL;

	crBegin(scp->chunk.crstate);
	scp->chunk.targisdir = 0;
    struct sftp_packet *pktout, *pktin;
    unsigned long remotever;
	scp->chunk.middle=NULL;
	scp->chunk.must_free_fname = 0;

	if (!scp->initialized) {

		pktout = sftp_pkt_init(SSH_FXP_INIT);
		sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
		sftp_send(pktout, scp);
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		if (pktin->type != SSH_FXP_VERSION) {
		fxp_internal_error("did not receive FXP_VERSION", scp);
			sftp_pkt_free(pktin);
			return;
		}
		if (!sftp_pkt_getuint32(pktin, &remotever)) {
		fxp_internal_error("malformed FXP_VERSION packet", scp);
			sftp_pkt_free(pktin);
		return;
		}
		if (remotever > SFTP_PROTO_VERSION) {
		fxp_internal_error
			("remote protocol is more advanced than we support", scp);
			sftp_pkt_free(pktin);
		return;
		}
		/*
		  * In principle, this packet might also contain extension-
		  * string pairs. We should work through them and look for any
		  * we recognise. In practice we don't currently do so because
		  * we know we don't recognise _any_.
		  */
		sftp_pkt_free(pktin);
		scp->initialized=TRUE;
	}
    if (scp->using_sftp) {
	char *newsource;

	/*
	 * It's possible that the source string we've been given
	 * contains a wildcard. If so, we must split the directory
	 * away from the wildcard itself (throwing an error if any
	 * wildcardness comes before the final slash) and arrange
	 * things so that a dirstack entry will be set up.
	 */
	newsource = memallocn(1+strlen(scp->remote), char);
	if (!wc_unescape(newsource, scp->remote)) {
	    /* Yes, here we go; it's a wildcard. Bah. */
	    char *dupsource, *lastpart, *dirpart, *wildcard;
	    dupsource = dupstr(scp->remote);
	    lastpart = stripslashes(dupsource, 0);
	    wildcard = dupstr(lastpart);
	    *lastpart = '\0';
	    if (*dupsource && dupsource[1]) {
		/*
		 * The remains of dupsource are at least two
		 * characters long, meaning the pathname wasn't
		 * empty or just `/'. Hence, we remove the trailing
		 * slash.
		 */
		lastpart[-1] = '\0';
	    } else if (!*dupsource) {
		/*
		 * The remains of dupsource are _empty_ - the whole
		 * pathname was a wildcard. Hence we need to
		 * replace it with ".".
		 */
		memfree(dupsource);
		dupsource = dupstr(".");
	    }

	    /*
	     * Now we have separated our string into dupsource (the
	     * directory part) and wildcard. Both of these will
	     * need freeing at some point. Next step is to remove
	     * wildcard escapes from the directory part, throwing
	     * an error if it contains a real wildcard.
	     */
	    dirpart = memallocn(1+strlen(dupsource), char);
	    if (!wc_unescape(dirpart, dupsource)) {
			p=dupprintf("%s: multiple-level wildcards unsupported",
				  scp->remote);

			memfree(dirpart);
			memfree(wildcard);
			memfree(dupsource);
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
			if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
			memfree(p);
			return;
	    }

	    /*
	     * Now we have dirpart (unescaped, ie a valid remote
	     * path), and wildcard (a wildcard). This will be
	     * sufficient to arrange a dirstack entry.
	     */
	    scp->scp_sftp_remotepath = dirpart;
	    scp->scp_sftp_wildcard = wildcard;
	    memfree(dupsource);
	} else {
	    scp->scp_sftp_remotepath = newsource;
	    scp->scp_sftp_wildcard = NULL;
	}
	scp->scp_sftp_preserve = scp->preserve;
	scp->scp_sftp_recursive = scp->recursive;
	scp->scp_sftp_donethistarget = 0;
	scp->scp_sftp_dirstack_head = NULL;
	}


    scp_sink_init(scp);

	while (1) {

	if (scp->using_sftp) {
	char *fname;
	int ret;

	if (!scp->scp_sftp_dirstack_head) {
	    if (!scp->scp_sftp_donethistarget) {
		/*
		 * Simple case: we are only dealing with one file.
		 */
		fname = scp->scp_sftp_remotepath;
		scp->chunk.must_free_fname = 0;
		scp->scp_sftp_donethistarget = 1;
	    } else {
		/*
		 * Even simpler case: one file _which we've done_.
		 * Return 1 (finished).
		 */
		break;
	    }
	} else {
	    /*
	     * We're now in the middle of stepping through a list
	     * of names returned from fxp_readdir(); so let's carry
	     * on.
	     */
		struct scp_tag::scp_sftp_dirstack *head = scp->scp_sftp_dirstack_head;
	    while (head->namepos < head->namelen &&
		   (is_dots(head->names[head->namepos].filename) ||
		    (head->wildcard &&
		     !wc_match(head->wildcard,
			       head->names[head->namepos].filename))))
		head->namepos++;       /* skip . and .. */
	    if (head->namepos < head->namelen) {
		head->matched_something = 1;
		fname = dupcat(head->dirpath, "/",
			       head->names[head->namepos++].filename,
			       NULL);
		scp->chunk.must_free_fname = 1;
	    } else {
		/*
		 * We've come to the end of the list; pop it off
		 * the stack and return an ENDDIR action (or RETRY
		 * if this was a wildcard match).
		 */
		if (head->wildcard) {
		    scp->chunk.act.action = SCP_SINK_RETRY;
		    if (!head->matched_something) {
				goto deldone;
		    }
		    memfree(head->wildcard);

		} else {
		    scp->chunk.act.action = SCP_SINK_ENDDIR;
		}

		memfree(head->dirpath);
		memfree(head->names);
		scp->scp_sftp_dirstack_head = head->next;
		memfree(head);

		goto act_cpt;
	    }
	}

	/*
	 * Now we have a filename. Stat it, and see if it's a file
	 * or a directory.
	 */
	scp->chunk.fname=fname;
	sftp_register(req = fxp_stat_send(fname,scp));
	scp->chunk.pkt_wait=1;
	crReturnV;
	pktin=scp->chunk.pkt;
	rreq = sftp_find_request(pktin, scp);
	ret = fxp_stat_recv(pktin, rreq, &scp->chunk.attrs, scp);

	if (!ret || !(scp->chunk.attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
		p=dupprintf("unable to identify %s: %s", scp->chunk.fname,
		      ret ? "file type not supplied" : fxp_error(scp));
		goto failure;
	}

	if (scp->chunk.attrs.permissions & 0040000) {
		struct scp_tag::scp_sftp_dirstack *newitem;
	    struct fxp_names *names;


		/*
	     * It's a directory. If we're not in recursive mode,
	     * this merits a complaint (which is fatal if the name
	     * was specified directly, but not if it was matched by
	     * a wildcard).
	     * 
	     * We skip this complaint completely if
	     * scp_sftp_wildcard is set, because that's an
	     * indication that we're not actually supposed to
	     * _recursively_ transfer the dir, just scan it for
	     * things matching the wildcard.
	     */
		if (!scp->scp_sftp_recursive && !scp->scp_sftp_wildcard) {
		p=dupprintf("pscp: %s: is a directory", scp->chunk.fname);
		goto failure;
	    }

	    /*
	     * Otherwise, the fun begins. We must fxp_opendir() the
	     * directory, slurp the filenames into memory, return
	     * SCP_SINK_DIR (unless this is a wildcard match), and
	     * set targetisdir. The next time we're called, we will
	     * run through the list of filenames one by one,
	     * matching them against a wildcard if present.
	     * 
	     * If targetisdir is _already_ set (meaning we're
	     * already in the middle of going through another such
	     * list), we must push the other (target,namelist) pair
	     * on a stack.
	     */
	    sftp_register(req = fxp_opendir_send(scp->chunk.fname, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
	    rreq = sftp_find_request(pktin, scp);
	    scp->chunk.dirhandle = fxp_opendir_recv(pktin, rreq, scp);

	    if (!scp->chunk.dirhandle) {
		p=dupprintf("scp: unable to open directory %s: %s",
			  scp->chunk.fname, fxp_error(scp));

		goto failure;
	    }
	    scp->chunk.nnames = scp->chunk.namesize = 0;
	    scp->chunk.ournames = NULL;
		while (1) {
		int i;

		sftp_register(req = fxp_readdir_send(scp->chunk.dirhandle, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		rreq = sftp_find_request(pktin, scp);
		names = fxp_readdir_recv(pktin, rreq, scp);

		if (names == NULL) {
		    if (fxp_error_type(scp) == SSH_FX_EOF)
			break;
			p=dupprintf("scp: reading directory %s: %s\n",
					  scp->chunk.fname, fxp_error(scp));

		    memfree(scp->chunk.ournames);
			goto failure;
		}
		if (names->nnames == 0) {
		    fxp_free_names(names);
		    break;
		}
		if (scp->chunk.nnames + names->nnames >= scp->chunk.namesize) {
		    scp->chunk.namesize += names->nnames + 128;
		    scp->chunk.ournames = memrealloc(scp->chunk.ournames, scp->chunk.namesize, struct fxp_name);
		}
		for (i = 0; i < names->nnames; i++) {
		    if (!strcmp(names->names[i].filename, ".") ||
			!strcmp(names->names[i].filename, "..")) {
			/*
			 * . and .. are normal consequences of
			 * reading a directory, and aren't worth
			 * complaining about.
			 */
		    } else if (!vet_filename(names->names[i].filename)) {
/*			tell_user(scp, "ignoring potentially dangerous server-"
				  "supplied filename '%S'\n",
				  names->names[i].filename);*/
				;
		    } else
			scp->chunk.ournames[scp->chunk.nnames++] = names->names[i];
		}
		names->nnames = 0;	       /* prevent free_names */
		fxp_free_names(names);
	    }
	    sftp_register(req = fxp_close_send(scp->chunk.dirhandle, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
	    rreq = sftp_find_request(pktin, scp);
	    fxp_close_recv(pktin, rreq, scp);

		newitem = memalloc(struct scp_tag::scp_sftp_dirstack);
	    newitem->next = scp->scp_sftp_dirstack_head;
	    newitem->names = scp->chunk.ournames;
	    newitem->namepos = 0;
	    newitem->namelen = scp->chunk.nnames;
		if (scp->chunk.must_free_fname)
		newitem->dirpath = scp->chunk.fname;
	    else
		newitem->dirpath = dupstr(scp->chunk.fname);
	    if (scp->scp_sftp_wildcard) {
		newitem->wildcard = scp->scp_sftp_wildcard;
		newitem->matched_something = 0;
		scp->scp_sftp_wildcard = NULL;
	    } else {
		newitem->wildcard = NULL;
	    }
	    scp->scp_sftp_dirstack_head = newitem;

	    if (newitem->wildcard) {
		scp->chunk.act.action = SCP_SINK_RETRY;
	    } else {
		scp->chunk.act.action = SCP_SINK_DIR;
		scp->chunk.act.buf = dupstr(stripslashes(scp->chunk.fname, 0));
		scp->chunk.act.name = scp->chunk.act.buf;
		}
		goto act_cpt;

	} else {

		/*
	     * It's a file. Return SCP_SINK_FILE.
	     */
	    scp->chunk.act.action = SCP_SINK_FILE;
	    scp->chunk.act.buf = dupstr(stripslashes(scp->chunk.fname, 0));
	    scp->chunk.act.name = scp->chunk.act.buf;
		if (scp->chunk.must_free_fname)
		scp->scp_sftp_currentname = scp->chunk.fname;
	    else
		scp->scp_sftp_currentname = dupstr(scp->chunk.fname);
		goto act_cpt;
	}

    } else {
	int done = 0;
	int i, bufsize;
	int action;
	char ch;

	scp->chunk.act.settime = 0;
	scp->chunk.act.buf = NULL;
	bufsize = 0;

	while (!done) {
		ssh_scp_recv((char *) &ch, 1,scp);
	    if (ch == '\n')
		{bump(scp,"Protocol error: Unexpected newline");return;}
	    i = 0;
	    action = ch;
	    do {
		ssh_scp_recv((char *) &ch, 1, scp);
		if (i >= bufsize) {
		    bufsize = i + 128;
		    scp->chunk.act.buf = memrealloc(scp->chunk.act.buf, bufsize, char);
		}
		scp->chunk.act.buf[i++] = ch;
	    } while (ch != '\n');
	    scp->chunk.act.buf[i - 1] = '\0';
	    switch (action) {
	      case '\01':		       /* error */
			  if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, scp->chunk.act.buf, strlen(scp->chunk.act.buf));
		goto act_cpt;		       /* go round again */
	      case '\02':		       /* fatal error */
			  {bump(scp,"%s", scp->chunk.act.buf);return;}
	      case 'E':
			  scp->ssh->protocol(scp->ssh, "", 1,  0, scp->channel_localid);
		scp->chunk.act.action = SCP_SINK_ENDDIR;
		goto act_cpt;
	      case 'T':
		if (scanf(scp->chunk.act.buf, "%ld %*d %ld %*d",
			   &scp->chunk.act.mtime, &scp->chunk.act.atime) == 2) {
		    scp->chunk.act.settime = 1;
			scp->ssh->protocol(scp->ssh, "", 1,  0, scp->channel_localid);
		    goto act_cpt;	       /* go round again */
		}
		{bump(scp,"Protocol error: Illegal time format");return;}
	      case 'C':
	      case 'D':
		scp->chunk.act.action = (action == 'C' ? SCP_SINK_FILE : SCP_SINK_DIR);
		break;
	      default:
			  {bump(scp,"Protocol error: Expected control record");return;}
	    }
	    /*
	     * We will go round this loop only once, unless we hit
	     * `continue' above.
	     */
	    done = 1;
	}

	/*
	 * If we get here, we must have seen SCP_SINK_FILE or
	 * SCP_SINK_DIR.
	 */
	{
		char sizestr[40];
	
	    if (scanf(scp->chunk.act.buf, "%o %s %n", &scp->chunk.act.mode, sizestr, &i) != 2)
		{bump(scp, "Protocol error: Illegal file descriptor format");return;}
	    scp->chunk.act.size = uint64_from_decimal(sizestr);
	    scp->chunk.act.name = scp->chunk.act.buf + i;
		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		return;
	}
	}
act_cpt:
	struct sftp_request *req;
	if (scp->chunk.act.action == SCP_SINK_ENDDIR)
	{
		struct scp_tag::scp_sftp_dirstack *head;
		head = scp->scp_sftp_dirstack_head;
		if (head==NULL) scp->chunk.fname=dupstr(scp->remote); else
		scp->chunk.fname = dupcat(head->dirpath, "/",
			       head->names[head->namepos-1].filename,
			       NULL);
		scp->chunk.must_free_fname = 1;

		sftp_register(req = fxp_rmdir_send(scp->chunk.fname, scp));
		scp->chunk.pkt_wait=1;
		crReturnV;
		pktin=scp->chunk.pkt;
		rreq = sftp_find_request(pktin, scp);
		int ret;
		ret = fxp_rmdir_recv(pktin, rreq, scp);

		if (!ret) {
			p=dupprintf("scp: can't remove directory %s: %s\n",
				scp->chunk.fname, fxp_error(scp));
			if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
			memfree(p);
failure:

			if (scp->chunk.must_free_fname) memfree(scp->chunk.fname);
			memfree(scp->chunk.middle);
			if (scp->chunk.act.buf) memfree(scp->chunk.act.buf);
			head = scp->scp_sftp_dirstack_head;
			while (scp->scp_sftp_dirstack_head) {
				memfree(head->dirpath);
				memfree(head->names);
				scp->scp_sftp_dirstack_head = head->next;
				memfree(head);
				head = scp->scp_sftp_dirstack_head;
			}
			if (scp->scp_sftp_remotepath) memfree(scp->scp_sftp_remotepath);
			scp->chunk.crstate=0;
			scp->chunk.type_wait=0;
			if (callback_proc[SCP_ERROR_INDEX]) callback_proc[SCP_ERROR_INDEX](scp->frontend, 0, scp->channel_localid, p, strlen(p));
			memfree(p);
			return;
		}
		if (scp->scp_sftp_dirstack_head) continue;
deldone:
		if (scp->chunk.must_free_fname) memfree(scp->chunk.fname);
		memfree(scp->scp_sftp_remotepath);

		scp->chunk.crstate=0;
		scp->chunk.type_wait=0;
		notify_threadqueue(scp->ssh,SCP_CALLBACK_COMPLETED,0,scp->channel_localid,NULL,0);
		return;
	}

	if (scp->chunk.act.action == SCP_SINK_RETRY)
	    continue;

	if (scp->chunk.act.action == SCP_SINK_DIR) {
		scp->chunk.targisdir=1;
	    continue;
	}

	if (scp->using_sftp) {
	struct sftp_packet *pktin;
	struct sftp_request *req, *rreq;

	sftp_register(req = fxp_remove_send(scp->scp_sftp_currentname, scp));
	scp->chunk.pkt_wait=1;
	crReturnV;
	pktin=scp->chunk.pkt;
	rreq = sftp_find_request(pktin, scp);
	int ret;
	ret = fxp_remove_recv(pktin, rreq, scp);

	if (!ret) {
		p=dupprintf("pscp: unable to remove %s: %s",
		      scp->scp_sftp_currentname, fxp_error(scp));
		goto failure;
	}
	memfree(scp->scp_sftp_currentname);
    } else {
		scp->ssh->protocol(scp->ssh, "", 1,  0, scp->channel_localid);
	}
	memfree(scp->chunk.act.buf);
	}
	crFinishV;
}

/* start executing new SCP command */
BOOL scp_command(void *handle, int proc) 
{

	scp_tag *scp = (scp_tag*)handle;
	if (scp->chunk.type_wait) return FALSE;
	scp->proc=proc;
	switch (scp->proc) {
	case SCP_DOWNLOAD:
		tolocal(scp);
		break;
	case SCP_UPLOAD:
		toremote(scp);
		break;
	case SCP_DIRLIST:
		getdir(scp);
		break;
	case SCP_MKDIR:
		mkdir(scp);
		break;
	case SCP_FSTAT:
		getstat(scp);
		break;
	case SCP_SET_STAT:
		setstat(scp);
		break;
	case SCP_DELETE:
		dodelete(scp);
		break;
	case SCP_RENAME:
		dorename(scp);
		break;
	default:
		return FALSE;
	}
	return TRUE;
}
void scp_channel_ready(void *frontend, int channel, const char *data, int len)
{
	scp_tag *scp = (scp_tag*)frontend;
	/*
     * Set up main and possibly fallback command depending on
     * options specified by user.
     * Attempt to start the SFTP subsystem as a first choice,
     * falling back to the provided scp command if that fails.
     */
	scp_packet_dispatch[SCP_DOWNLOAD]=tolocal;
	scp_packet_dispatch[SCP_UPLOAD]=toremote;
	scp_packet_dispatch[SCP_DIRLIST]=getdir;
	scp_packet_dispatch[SCP_MKDIR]=mkdir;
	scp_packet_dispatch[SCP_FSTAT]=getstat;
	scp_packet_dispatch[SCP_RENAME]=dorename;
	scp_packet_dispatch[SCP_SET_STAT]=setstat;
	scp_packet_dispatch[SCP_DELETE]=dodelete;

	switch (scp->proc) {
	case SCP_DOWNLOAD:
		tolocal(scp);
		break;
	case SCP_UPLOAD:
		toremote(scp);
		break;
	case SCP_DIRLIST:
		getdir(scp);
		break;
	case SCP_MKDIR:
		mkdir(scp);
		break;
	case SCP_FSTAT:
		getstat(scp);
		break;
	case SCP_SET_STAT:
		setstat(scp);
		break;
	case SCP_DELETE:
		dodelete(scp);
		break;
	case SCP_RENAME:
		dorename(scp);
		break;
	case SCP_IDLE:					/* do nothing, just report and wait */
		notify_threadqueue(scp->ssh,SCP_CALLBACK_COMPLETED,0,scp->channel_localid,NULL,0);
		break;
	} 
}

void scp_get_data(scp_tag *scp, const char *data, int datalen) {
		char *p = (char *) data;
		unsigned len = (unsigned) datalen;

		if ((scp->outlen > 0) && (len > 0)) {
			unsigned used = scp->outlen;
			if (used > len)
				used = len;
			memcpy(scp->outptr, p, used);
			scp->outptr += used;
			scp->outlen -= used;
			p += used;
			len -= used;
		}

		if (len > 0) {
		if (scp->pendsize < scp->pendlen + len) {
			scp->pendsize = scp->pendlen + len + 4096;
			scp->pending = memrealloc(scp->pending, scp->pendsize, char);
		}
		memcpy(scp->pending + scp->pendlen, p, len);
		scp->pendlen += len;
		}
}

void scp_channel_data(void *frontend, int channel, const char *data, int datalen) 
{
	scp_tag *scp = (scp_tag*)frontend;
	scp_get_data(scp,data,datalen);

	do {
			switch (scp->chunk.pkt_wait) {
				case 1:
					ssh_scp_recv(scp->chunk.x, 4, scp);
					scp->chunk.pkt_wait=2;
					break;
				case 2:
					scp->chunk.pkt = memalloc(struct sftp_packet);
					scp->chunk.pkt->savedpos = 0;
					scp->chunk.pkt->length = scp->chunk.pkt->maxlen = GET_32BIT(scp->chunk.x);
					scp->chunk.pkt->data = memallocn(scp->chunk.pkt->length, char);
					ssh_scp_recv(scp->chunk.pkt->data, scp->chunk.pkt->length, scp);

					scp->chunk.pkt_wait=3;
					break;
			}
			scp_buffer_consume(scp);
		} 	while (scp->chunk.pkt_wait && scp->pendlen>0 && scp->pendlen>scp->outlen);
}

void scp_channel_failure(void *frontend, int channel, const char *data, int len) 
{
	scp_tag *scp = (scp_tag*)frontend;
	if (callback_proc[SCP_SHUTDOWN_INDEX]) callback_proc[SCP_SHUTDOWN_INDEX](scp->frontend, 0, channel, data, len);
}

void scp_channel_closed(void *frontend, int channel, const char *data, int len) 
{
	scp_tag *scp = (scp_tag*)frontend;
	if (callback_proc[SCP_SHUTDOWN_INDEX]) callback_proc[SCP_SHUTDOWN_INDEX](scp->frontend, 0, channel, data, len);
}

/* now this is the second part: SCP
* frontend can call scp_init as many times as it wants.
* with every new call a channel will be opened and asynchronous 
* process started if backend_handle is not null.
* The frontend will know about progress only by the callbacks
* the callbacks will be called from here, in SSH message queue 
* thread. The problem is: SCP protocol is synchronous. SSH
* protocol is not. Since we are dealing with callbacks, all SCP/SFTP 
* protocol implementation will work on the callbacks. We cannot 
* simply send packed and wait for response, because it will stop all 
* thread message queue and block all callbacks in other channels.
* so - we send a packet and return immediately. When response is 
* received, we dispatch it to neccessary function.
* ADDED: Fully asynchronous operating shows big packet response 
* time and therefore greatly decreases maximum transfer speed. 
* I have no idea how to increase speed of SCP subsystem if 
* sending/receiving happen in separate threads. I switched back to partially 
* synchronous mode to get off the delays during WaitForSingleObject. 
* Now all scp work lays on backend thread shoulders.
* Perfomance tests show x36 times speed increase!
*/

const wchar_t *scp_init(void *frontend_handle, void **backend_handle,
			    Config *cfg,
			    char *host, int port, char **realhost, int nodelay,
			    int keepalive, int proc, char *remote, wchar_t *local)
{
	scp_tag *scp;
	scp = memalloc(scp_tag);
	memset(scp,0,sizeof(scp_tag));
	scp->magic = 'SFTP';										/* do not mess up with handles! */

	Ssh ssh= *((Ssh*)backend_handle);
	scp->ssh=ssh;

	scp->cfg=*cfg;

	scp->try_scp = 1;		/*	hmmm.. that's funny ;) */
	scp->try_sftp = 1;
    scp->cfg.remote_cmd_ptr2 = NULL;

	scp->recursive=1;
	scp->preserve=1;
	scp->local=local;
	scp->remote=remote;
	scp->frontend=frontend_handle;

	if (proc==SCP_DIRLIST) scp->targetshouldbedirectory=1;

    char *cmd = dupprintf("scp%s%s%s%s -t %s",
	    scp->recursive ? " -r" : "",
	    scp->preserve ? " -p" : "",
		scp->targetshouldbedirectory ? " -d" : "", remote);

    if (scp->try_sftp) {
	/* First choice is SFTP subsystem. */
	scp->main_cmd_is_sftp = 1;
	strcpy(scp->cfg.remote_cmd, "sftp");
	scp->cfg.ssh_subsys = TRUE;
	if (scp->try_scp) {
	    /* Fallback is to use the provided scp command. */
	    scp->fallback_cmd_is_sftp = 0;
	    scp->cfg.remote_cmd_ptr2 = cmd;
	    scp->cfg.ssh_subsys2 = FALSE;
	} else {
	    /* Since we're not going to try SCP, we may as well try
	     * harder to find an SFTP server, since in the current
	     * implementation we have a spare slot. */
	    scp->fallback_cmd_is_sftp = 1;
	    /* see psftp.c for full explanation of this kludge */
	    scp->cfg.remote_cmd_ptr2 = 
		"test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
		"test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
		"exec sftp-server";
	    scp->cfg.ssh_subsys2 = FALSE;
	}
    } else {
	/* Don't try SFTP at all; just try the scp command. */
	scp->main_cmd_is_sftp = 0;
	scp->cfg.remote_cmd_ptr = cmd;
	scp->cfg.ssh_subsys = FALSE;
    }
    scp->cfg.nopty = TRUE;

	if (scp->ssh) {
		if (((scp_tag*)ssh)->magic == 'SFTP') scp->ssh=((scp_tag*)ssh)->ssh;		// we have received backhandle to another SCP
		back->reconfig(scp->ssh,cfg);
		scp->channel_localid=back->create_channel(scp->ssh,scp);
		if (scp->channel_localid == -1) {
			memfree(scp);
			return L"Cannot allocate channel";
		}
	} else {
		strcpy(scp->cfg.host,host);
		scp->cfg.port=port;
		const wchar_t *err = back->init(scp, (void**)&scp->ssh, &scp->cfg, scp->cfg.host, scp->cfg.port, realhost, 
				 0, scp->cfg.tcp_keepalives);

		scp->channel_localid=0;

		if (err != NULL)
		{bump(scp, "ssh_init: %s", err);return err;}

		/* Work out which backend we ended up using. */
		if (!ssh_fallback_cmd(scp->ssh))
		scp->using_sftp = scp->main_cmd_is_sftp;
		else
		scp->using_sftp = scp->fallback_cmd_is_sftp;

		//memfree(realhost);
	}
	scp->proc=proc;

	return NULL;
}

void scp_buffer_consume(scp_tag *scp) 
{
    /*
     * See if the pending-input block contains some of what we
     * need.
     */
    if (scp->pendlen > 0) {
	unsigned pendused = scp->pendlen;
	if (pendused > scp->outlen)
	    pendused = scp->outlen;
	memcpy(scp->outptr, scp->pending, pendused);
	memmove(scp->pending, scp->pending + pendused, scp->pendlen - pendused);
	scp->outptr += pendused;
	scp->outlen -= pendused;
	scp->pendlen -= pendused;
	if (scp->pendlen == 0) {
	    scp->pendsize = 0;
	    memfree(scp->pending);
	    scp->pending = NULL;
	}
	}
	char uc;
	if (scp->chunk.pkt_wait==3 && scp->outlen==0) {
			if (!sftp_pkt_getbyte(scp->chunk.pkt, &uc)) {
				sftp_pkt_free(scp->chunk.pkt);
			} else {
				scp->chunk.pkt->type = uc;
			}
			scp->chunk.pkt_wait=0;
	}
	if (scp->chunk.pkt_wait==0) scp_packet_dispatch[scp->chunk.type_wait](scp);
}


void scp_free(void *handle) {
	scp_tag *scp=(scp_tag*)handle;
	back->free(scp->ssh);
}

Subsystem scp_backend = {
    scp_init,
	scp_free,
	scp_command,
    22
};
